mechanize-2.10.1/ 0000755 0000041 0000041 00000000000 14645745627 013612 5 ustar www-data www-data mechanize-2.10.1/.autotest 0000644 0000041 0000041 00000000151 14645745627 015460 0 ustar www-data www-data require 'autotest/restart'
Autotest.add_hook :initialize do |at|
at.testlib = 'minitest/autorun'
end
mechanize-2.10.1/.gitignore 0000644 0000041 0000041 00000000164 14645745627 015603 0 ustar www-data www-data *.swp
*.gem
*.rbc
/.bundle
/.config
/Gemfile.lock
/TAGS
/pkg
/tags
/.rvmrc
digest.htpasswd
doc
rdoc
.yardoc
_yardoc
mechanize-2.10.1/.github/ 0000755 0000041 0000041 00000000000 14645745627 015152 5 ustar www-data www-data mechanize-2.10.1/.github/dependabot.yml 0000644 0000041 0000041 00000000662 14645745627 020006 0 ustar www-data www-data # To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "bundler"
directory: "/"
schedule:
interval: "weekly"
mechanize-2.10.1/.github/workflows/ 0000755 0000041 0000041 00000000000 14645745627 017207 5 ustar www-data www-data mechanize-2.10.1/.github/workflows/upstream.yml 0000644 0000041 0000041 00000002331 14645745627 021571 0 ustar www-data www-data name: "upstream"
concurrency:
group: "${{github.workflow}}-${{github.ref}}"
cancel-in-progress: true
on:
workflow_dispatch:
schedule:
- cron: "0 8 * * 5" # At 08:00 on Friday # https://crontab.guru/#0_8_*_*_5
push:
branches:
- main
pull_request:
types: [opened, synchronize]
branches:
- main
paths:
- .github/workflows/upstream.yml # this file
jobs:
test:
strategy:
fail-fast: false
matrix:
ruby-version: ["head", "jruby-head", "truffleruby-head"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{matrix.ruby-version}}
bundler-cache: true
- run: bundle exec rake test
upstream:
name: "upstream (${{matrix.name}})"
strategy:
fail-fast: false
matrix:
include:
- { name: "nokogiri", git: "https://github.com/sparklemotion/nokogiri" }
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with: { ruby-version: "3.3" }
- run: |
bundle add ${{matrix.name}} --git="${{matrix.git}}"
bundle show
- run: bundle exec rake test
mechanize-2.10.1/.github/workflows/ci.yml 0000644 0000041 0000041 00000001664 14645745627 020334 0 ustar www-data www-data name: "ci"
concurrency:
group: "${{github.workflow}}-${{github.ref}}"
cancel-in-progress: true
on:
workflow_dispatch:
schedule:
- cron: "0 8 * * 5" # At 08:00 on Friday # https://crontab.guru/#0_8_*_*_5
push:
branches:
- main
pull_request:
types: [opened, synchronize]
branches:
- main
jobs:
rubocop:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true
- run: bundle exec rake rubocop
test:
needs: ["rubocop"]
strategy:
fail-fast: false
matrix:
ruby-version: ["2.6", "2.7", "3.0", "3.1", "3.2", "3.3", "jruby-9.4", "truffleruby"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{matrix.ruby-version}}
bundler-cache: true
- run: bundle exec rake test
mechanize-2.10.1/lib/ 0000755 0000041 0000041 00000000000 14645745627 014360 5 ustar www-data www-data mechanize-2.10.1/lib/mechanize.rb 0000644 0000041 0000041 00000106150 14645745627 016653 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/version'
require 'fileutils'
require 'forwardable'
require 'net/http/digest_auth'
require 'net/http/persistent'
require 'nokogiri'
require 'openssl'
require 'pp'
require 'stringio'
require 'uri'
require 'webrick/httputils'
require 'zlib'
##
# The Mechanize library is used for automating interactions with a website. It
# can follow links and submit forms. Form fields can be populated and
# submitted. A history of URLs is maintained and can be queried.
#
# == Example
#
# require 'mechanize'
# require 'logger'
#
# agent = Mechanize.new
# agent.log = Logger.new "mech.log"
# agent.user_agent_alias = 'Mac Safari'
#
# page = agent.get "http://www.google.com/"
# search_form = page.form_with :name => "f"
# search_form.field_with(:name => "q").value = "Hello"
#
# search_results = agent.submit search_form
# puts search_results.body
#
# == Issues with mechanize
#
# If you think you have a bug with mechanize, but aren't sure, please file a
# ticket at https://github.com/sparklemotion/mechanize/issues
#
# Here are some common problems you may experience with mechanize
#
# === Problems connecting to SSL sites
#
# Mechanize defaults to validating SSL certificates using the default CA
# certificates for your platform. At this time, Windows users do not have
# integration between the OS default CA certificates and OpenSSL. #cert_store
# explains how to download and use Mozilla's CA certificates to allow SSL
# sites to work.
#
# === Problems with content-length
#
# Some sites return an incorrect content-length value. Unlike a browser,
# mechanize raises an error when the content-length header does not match the
# response length since it does not know if there was a connection problem or
# if the mismatch is a server bug.
#
# The error raised, Mechanize::ResponseReadError, can be converted to a parsed
# Page, File, etc. depending upon the content-type:
#
# agent = Mechanize.new
# uri = URI 'http://example/invalid_content_length'
#
# begin
# page = agent.get uri
# rescue Mechanize::ResponseReadError => e
# page = e.force_parse
# end
class Mechanize
##
# Base mechanize error class
class Error < RuntimeError
end
ruby_version = if RUBY_PATCHLEVEL >= 0 then
"#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
else
"#{RUBY_VERSION}dev#{RUBY_REVISION}"
end
##
# Supported User-Agent aliases for use with user_agent_alias=. The
# description in parenthesis is for informative purposes and is not part of
# the alias name.
#
# The default User-Agent alias:
#
# * "Mechanize"
#
# Linux User-Agent aliases:
#
# * "Linux Firefox"
# * "Linux Konqueror"
# * "Linux Mozilla"
#
# Mac User-Agent aliases:
#
# * "Mac Firefox"
# * "Mac Mozilla"
# * "Mac Safari 4"
# * "Mac Safari"
#
# Windows User-Agent aliases:
#
# * "Windows Chrome"
# * "Windows Edge"
# * "Windows Firefox"
# * "Windows IE 6"
# * "Windows IE 7"
# * "Windows IE 8"
# * "Windows IE 9"
# * "Windows IE 10"
# * "Windows IE 11"
# * "Windows Mozilla"
#
# Mobile User-Agent aliases:
#
# * "Android"
# * "iPad"
# * "iPhone"
#
# Example:
#
# agent = Mechanize.new
# agent.user_agent_alias = 'Mac Safari'
#
AGENT_ALIASES = {
'Mechanize' => "Mechanize/#{VERSION} Ruby/#{ruby_version} (http://github.com/sparklemotion/mechanize/)",
'Linux Firefox' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/121.0',
'Linux Konqueror' => 'Mozilla/5.0 (compatible; Konqueror/3; Linux)',
'Linux Mozilla' => 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.4) Gecko/20030624',
'Mac Firefox' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14.2; rv:109.0) Gecko/20100101 Firefox/121.0',
'Mac Mozilla' => 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.4a) Gecko/20030401',
'Mac Safari 4' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; de-at) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10',
'Mac Safari' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
'Windows Chrome' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Windows Edge' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.2210.133',
'Windows Firefox' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0',
'Windows IE 6' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
'Windows IE 7' => 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
'Windows IE 8' => 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
'Windows IE 9' => 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)',
'Windows IE 10' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)',
'Windows IE 11' => 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko',
'Windows Mozilla' => 'Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4b) Gecko/20030516 Mozilla Firebird/0.6',
'Android' => 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.210 Mobile Safari/537.36',
'iPad' => 'Mozilla/5.0 (iPad; CPU OS 17_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
'iPhone' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
}
AGENT_ALIASES.default_proc = proc { |hash, key|
case key
when /FireFox/
if ua = hash[nkey = key.sub(/FireFox/, 'Firefox')]
warn "Mechanize#user_agent_alias: #{key.inspect} should be spelled as #{nkey.inspect}"
ua
end
end
}
def self.inherited(child) # :nodoc:
child.html_parser = html_parser
child.log = log
super
end
##
# Creates a new Mechanize instance and yields it to the given block.
#
# After the block executes, the instance is cleaned up. This includes
# closing all open connections.
#
# Mechanize.start do |m|
# m.get("http://example.com")
# end
def self.start
instance = new
yield(instance)
ensure
instance.shutdown
end
##
# Creates a new mechanize instance. If a block is given, the created
# instance is yielded to the block for setting up pre-connection state such
# as SSL parameters or proxies:
#
# agent = Mechanize.new do |a|
# a.proxy_addr = 'proxy.example'
# a.proxy_port = 8080
# end
#
# If you need segregated SSL connections give each agent a unique
# name. Otherwise the connections will be shared. This is
# particularly important if you are using certificates.
#
# agent_1 = Mechanize.new 'conn1'
# agent_2 = Mechanize.new 'conn2'
#
def initialize(connection_name = 'mechanize')
@agent = Mechanize::HTTP::Agent.new(connection_name)
@agent.context = self
@log = nil
# attr_accessors
@agent.user_agent = AGENT_ALIASES['Mechanize']
@watch_for_set = nil
@history_added = nil
# attr_readers
@pluggable_parser = PluggableParser.new
@keep_alive_time = 0
# Proxy
@proxy_addr = nil
@proxy_port = nil
@proxy_user = nil
@proxy_pass = nil
@html_parser = self.class.html_parser
@default_encoding = nil
@force_default_encoding = false
# defaults
@agent.max_history = 50
yield self if block_given?
@agent.set_proxy @proxy_addr, @proxy_port, @proxy_user, @proxy_pass
end
# :section: History
#
# Methods for navigating and controlling history
##
# Equivalent to the browser back button. Returns the previous page visited.
def back
@agent.history.pop
end
##
# Returns the latest page loaded by Mechanize
def current_page
@agent.current_page
end
alias page current_page
##
# The history of this mechanize run
def history
@agent.history
end
##
# Maximum number of items allowed in the history. The default setting is 50
# pages. Note that the size of the history multiplied by the maximum
# response body size
def max_history
@agent.history.max_size
end
##
# Sets the maximum number of items allowed in the history to +length+.
#
# Setting the maximum history length to nil will make the history size
# unlimited. Take care when doing this, mechanize stores response bodies in
# memory for pages and in the temporary files directory for other responses.
# For a long-running mechanize program this can be quite large.
#
# See also the discussion under #max_file_buffer=
def max_history= length
@agent.history.max_size = length
end
##
# Returns a visited page for the +url+ passed in, otherwise nil
def visited? url
url = url.href if url.respond_to? :href
@agent.visited_page url
end
##
# Returns whether or not a url has been visited
alias visited_page visited?
# :section: Hooks
#
# Hooks into the operation of mechanize
##
# A list of hooks to call before reading response header 'content-encoding'.
#
# The hook is called with the agent making the request, the URI of the
# request, the response an IO containing the response body.
def content_encoding_hooks
@agent.content_encoding_hooks
end
##
# Callback which is invoked with the page that was added to history.
attr_accessor :history_added
##
# A list of hooks to call after retrieving a response. Hooks are called with
# the agent, the URI, the response, and the response body.
def post_connect_hooks
@agent.post_connect_hooks
end
##
# A list of hooks to call before retrieving a response. Hooks are called
# with the agent, the URI, the response, and the response body.
def pre_connect_hooks
@agent.pre_connect_hooks
end
# :section: Requests
#
# Methods for making HTTP requests
##
# If the parameter is a string, finds the button or link with the
# value of the string on the current page and clicks it. Otherwise, clicks
# the Mechanize::Page::Link object passed in. Returns the page fetched.
def click link
case link
when Page::Link then
referer = link.page || current_page()
if @agent.robots
if (referer.is_a?(Page) and referer.parser.nofollow?) or
link.rel?('nofollow') then
raise RobotsDisallowedError.new(link.href)
end
end
if link.noreferrer?
href = @agent.resolve(link.href, link.page || current_page)
referer = Page.new
else
href = link.href
end
get href, [], referer
when String, Regexp then
if real_link = page.link_with(:text => link)
click real_link
else
button = nil
# Note that this will not work if we have since navigated to a different page.
# Should rather make each button aware of its parent form.
form = page.forms.find do |f|
button = f.button_with(:value => link)
button.is_a? Form::Submit
end
submit form, button if form
end
when Form::Submit, Form::ImageButton then
# Note that this will not work if we have since navigated to a different page.
# Should rather make each button aware of its parent form.
form = page.forms.find do |f|
f.buttons.include?(link)
end
submit form, link if form
else
referer = current_page()
href = link.respond_to?(:href) ? link.href :
(link['href'] || link['src'])
get href, [], referer
end
end
##
# GETs +uri+ and writes it to +io_or_filename+ without recording the request
# in the history. If +io_or_filename+ does not respond to #write it will be
# used as a file name. +parameters+, +referer+ and +headers+ are used as in
# #get.
#
# By default, if the Content-type of the response matches a Mechanize::File
# or Mechanize::Page parser, the response body will be loaded into memory
# before being saved. See #pluggable_parser for details on changing this
# default.
#
# For alternate ways of downloading files see Mechanize::FileSaver and
# Mechanize::DirectorySaver.
def download uri, io_or_filename, parameters = [], referer = nil, headers = {}
page = transact do
get uri, parameters, referer, headers
end
io = if io_or_filename.respond_to? :write then
io_or_filename
else
::File.open(io_or_filename, 'wb')
end
case page
when Mechanize::File then
io.write page.body
else
body_io = page.body_io
until body_io.eof? do
io.write body_io.read 16384
end
end
page
ensure
io.close if io and not io_or_filename.respond_to? :write
end
##
# DELETE +uri+ with +query_params+, and setting +headers+:
#
# +query_params+ is formatted into a query string using
# Mechanize::Util.build_query_string, which see.
#
# delete('http://example/', {'q' => 'foo'}, {})
def delete(uri, query_params = {}, headers = {})
page = @agent.fetch(uri, :delete, headers, query_params)
add_to_history(page)
page
end
##
# GET the +uri+ with the given request +parameters+, +referer+ and
# +headers+.
#
# The +referer+ may be a URI or a page.
#
# +parameters+ is formatted into a query string using
# Mechanize::Util.build_query_string, which see.
def get(uri, parameters = [], referer = nil, headers = {})
method = :get
referer ||=
if uri.to_s =~ %r{\Ahttps?://}
Page.new
else
current_page || Page.new
end
# FIXME: Huge hack so that using a URI as a referer works. I need to
# refactor everything to pass around URIs but still support
# Mechanize::Page#base
unless Mechanize::Parser === referer then
referer = if referer.is_a?(String) then
Page.new URI(referer)
else
Page.new referer
end
end
# fetch the page
headers ||= {}
page = @agent.fetch uri, method, headers, parameters, referer
add_to_history(page)
yield page if block_given?
page
end
##
# GET +url+ and return only its contents
def get_file(url)
get(url).body
end
##
# HEAD +uri+ with +query_params+ and +headers+:
#
# +query_params+ is formatted into a query string using
# Mechanize::Util.build_query_string, which see.
#
# head('http://example/', {'q' => 'foo'}, {})
def head(uri, query_params = {}, headers = {})
page = @agent.fetch uri, :head, headers, query_params
yield page if block_given?
page
end
##
# POST to the given +uri+ with the given +query+.
#
# +query+ is processed using Mechanize::Util.each_parameter (which
# see), and then encoded into an entity body. If any IO/FileUpload
# object is specified as a field value the "enctype" will be
# multipart/form-data, or application/x-www-form-urlencoded
# otherwise.
#
# Examples:
# agent.post 'http://example.com/', "foo" => "bar"
#
# agent.post 'http://example.com/', [%w[foo bar]]
#
# agent.post('http://example.com/', "hello",
# 'Content-Type' => 'application/xml')
def post(uri, query = {}, headers = {})
return request_with_entity(:post, uri, query, headers) if String === query
node = {}
# Create a fake form
class << node
def search(*args); []; end
end
node['method'] = 'POST'
node['enctype'] = 'application/x-www-form-urlencoded'
form = Form.new(node)
Mechanize::Util.each_parameter(query) { |k, v|
if v.is_a?(IO)
form.enctype = 'multipart/form-data'
ul = Form::FileUpload.new({'name' => k.to_s},::File.basename(v.path))
ul.file_data = v.read
form.file_uploads << ul
elsif v.is_a?(Form::FileUpload)
form.enctype = 'multipart/form-data'
form.file_uploads << v
else
form.fields << Form::Field.new({'name' => k.to_s},v)
end
}
post_form(uri, form, headers)
end
##
# PUT to +uri+ with +entity+, and setting +headers+:
#
# put('http://example/', 'new content', {'Content-Type' => 'text/plain'})
def put(uri, entity, headers = {})
request_with_entity(:put, uri, entity, headers)
end
##
# Makes an HTTP request to +url+ using HTTP method +verb+. +entity+ is used
# as the request body, if allowed.
def request_with_entity(verb, uri, entity, headers = {})
cur_page = current_page || Page.new
log.debug("query: #{ entity.inspect }") if log
headers = {
'Content-Type' => 'application/octet-stream',
'Content-Length' => entity.size.to_s,
}.update headers
page = @agent.fetch uri, verb, headers, [entity], cur_page
add_to_history(page)
page
end
##
# Submits +form+ with an optional +button+.
#
# Without a button:
#
# page = agent.get('http://example.com')
# agent.submit(page.forms.first)
#
# With a button:
#
# agent.submit(page.forms.first, page.forms.first.buttons.first)
def submit(form, button = nil, headers = {})
form.add_button_to_query(button) if button
case form.method.upcase
when 'POST'
post_form(form.action, form, headers)
when 'GET'
get(form.action.gsub(/\?[^\?]*$/, ''),
form.build_query,
form.page,
headers)
else
raise ArgumentError, "unsupported method: #{form.method.upcase}"
end
end
##
# Runs given block, then resets the page history as it was before. self is
# given as a parameter to the block. Returns the value of the block.
def transact
history_backup = @agent.history.dup
begin
yield self
ensure
@agent.history = history_backup
end
end
# :section: Settings
#
# Settings that adjust how mechanize makes HTTP requests including timeouts,
# keep-alives, compression, redirects and headers.
@html_parser = Nokogiri::HTML
@log = nil
class << self
##
# Default HTML parser for all mechanize instances
#
# Mechanize.html_parser = Nokogiri::XML
attr_accessor :html_parser
##
# Default logger for all mechanize instances
#
# Mechanize.log = Logger.new $stderr
attr_accessor :log
end
##
# A default encoding name used when parsing HTML parsing. When set it is
# used after any other encoding. The default is nil.
attr_accessor :default_encoding
##
# Overrides the encodings given by the HTTP server and the HTML page with
# the default_encoding when set to true.
attr_accessor :force_default_encoding
##
# The HTML parser to be used when parsing documents
attr_accessor :html_parser
##
# HTTP/1.0 keep-alive time. This is no longer supported by mechanize as it
# now uses net-http-persistent which only supports HTTP/1.1 persistent
# connections
attr_accessor :keep_alive_time
##
# The pluggable parser maps a response Content-Type to a parser class. The
# registered Content-Type may be either a full content type like 'image/png'
# or a media type 'text'. See Mechanize::PluggableParser for further
# details.
#
# Example:
#
# agent.pluggable_parser['application/octet-stream'] = Mechanize::Download
attr_reader :pluggable_parser
##
# The HTTP proxy address
attr_reader :proxy_addr
##
# The HTTP proxy password
attr_reader :proxy_pass
##
# The HTTP proxy port
attr_reader :proxy_port
##
# The HTTP proxy username
attr_reader :proxy_user
##
# *NOTE*: These credentials will be used as a default for any challenge
# exposing your password to disclosure to malicious servers. Use of this
# method will warn. This method is deprecated and will be removed in
# mechanize 3.
#
# Sets the +user+ and +password+ as the default credentials to be used for
# HTTP authentication for any server. The +domain+ is used for NTLM
# authentication.
def auth user, password, domain = nil
c = caller_locations(1,1).first
warn <<-WARNING
At #{c.absolute_path} line #{c.lineno}
Use of #auth and #basic_auth are deprecated due to a security vulnerability.
WARNING
@agent.add_default_auth user, password, domain
end
alias basic_auth auth
##
# Adds credentials +user+, +pass+ for +uri+. If +realm+ is set the
# credentials are used only for that realm. If +realm+ is not set the
# credentials become the default for any realm on that URI.
#
# +domain+ and +realm+ are exclusive as NTLM does not follow RFC 2617. If
# +domain+ is given it is only used for NTLM authentication.
def add_auth uri, user, password, realm = nil, domain = nil
@agent.add_auth uri, user, password, realm, domain
end
##
# Are If-Modified-Since conditional requests enabled?
def conditional_requests
@agent.conditional_requests
end
##
# Disables If-Modified-Since conditional requests (enabled by default)
def conditional_requests= enabled
@agent.conditional_requests = enabled
end
##
# A Mechanize::CookieJar which stores cookies
def cookie_jar
@agent.cookie_jar
end
##
# Replaces the cookie jar with +cookie_jar+
def cookie_jar= cookie_jar
@agent.cookie_jar = cookie_jar
end
##
# Returns a list of cookies stored in the cookie jar.
def cookies
@agent.cookie_jar.to_a
end
##
# Follow HTML meta refresh and HTTP Refresh headers. If set to +:anywhere+
# meta refresh tags outside of the head element will be followed.
def follow_meta_refresh
@agent.follow_meta_refresh
end
##
# Controls following of HTML meta refresh and HTTP Refresh headers in
# responses.
def follow_meta_refresh= follow
@agent.follow_meta_refresh = follow
end
##
# Follow an HTML meta refresh and HTTP Refresh headers that have no "url="
# in the content attribute.
#
# Defaults to false to prevent infinite refresh loops.
def follow_meta_refresh_self
@agent.follow_meta_refresh_self
end
##
# Alters the following of HTML meta refresh and HTTP Refresh headers that
# point to the same page.
def follow_meta_refresh_self= follow
@agent.follow_meta_refresh_self = follow
end
##
# Is gzip compression of responses enabled?
def gzip_enabled
@agent.gzip_enabled
end
##
# Disables HTTP/1.1 gzip compression (enabled by default)
def gzip_enabled=enabled
@agent.gzip_enabled = enabled
end
##
# Connections that have not been used in this many seconds will be reset.
def idle_timeout
@agent.idle_timeout
end
# Sets the idle timeout to +idle_timeout+. The default timeout is 5
# seconds. If you experience "too many connection resets", reducing this
# value may help.
def idle_timeout= idle_timeout
@agent.idle_timeout = idle_timeout
end
##
# When set to true mechanize will ignore an EOF during chunked transfer
# encoding so long as at least one byte was received. Be careful when
# enabling this as it may cause data loss.
#
# Net::HTTP does not inform mechanize of where in the chunked stream the EOF
# occurred. Usually it is after the last-chunk but before the terminating
# CRLF (invalid termination) but it may occur earlier. In the second case
# your response body may be incomplete.
def ignore_bad_chunking
@agent.ignore_bad_chunking
end
##
# When set to true mechanize will ignore an EOF during chunked transfer
# encoding. See ignore_bad_chunking for further details
def ignore_bad_chunking= ignore_bad_chunking
@agent.ignore_bad_chunking = ignore_bad_chunking
end
##
# Are HTTP/1.1 keep-alive connections enabled?
def keep_alive
@agent.keep_alive
end
##
# Disable HTTP/1.1 keep-alive connections if +enable+ is set to false. If
# you are experiencing "too many connection resets" errors setting this to
# false will eliminate them.
#
# You should first investigate reducing idle_timeout.
def keep_alive= enable
@agent.keep_alive = enable
end
##
# The current logger. If no logger has been set Mechanize.log is used.
def log
@log || Mechanize.log
end
##
# Sets the +logger+ used by this instance of mechanize
def log= logger
@log = logger
end
##
# Responses larger than this will be written to a Tempfile instead of stored
# in memory. The default is 100,000 bytes.
#
# A value of nil disables creation of Tempfiles.
def max_file_buffer
@agent.max_file_buffer
end
##
# Sets the maximum size of a response body that will be stored in memory to
# +bytes+. A value of nil causes all response bodies to be stored in
# memory.
#
# Note that for Mechanize::Download subclasses, the maximum buffer size
# multiplied by the number of pages stored in history (controlled by
# #max_history) is an approximate upper limit on the amount of memory
# Mechanize will use. By default, Mechanize can use up to ~5MB to store
# response bodies for non-File and non-Page (HTML) responses.
#
# See also the discussion under #max_history=
def max_file_buffer= bytes
@agent.max_file_buffer = bytes
end
##
# Length of time to wait until a connection is opened in seconds
def open_timeout
@agent.open_timeout
end
##
# Sets the connection open timeout to +open_timeout+
def open_timeout= open_timeout
@agent.open_timeout = open_timeout
end
##
# Length of time to wait for data from the server
def read_timeout
@agent.read_timeout
end
##
# Sets the timeout for each chunk of data read from the server to
# +read_timeout+. A single request may read many chunks of data.
def read_timeout= read_timeout
@agent.read_timeout = read_timeout
end
##
# Controls how mechanize deals with redirects. The following values are
# allowed:
#
# :all, true:: All 3xx redirects are followed (default)
# :permanent:: Only 301 Moved Permanently redirects are followed
# false:: No redirects are followed
def redirect_ok
@agent.redirect_ok
end
alias follow_redirect? redirect_ok
##
# Sets the mechanize redirect handling policy. See redirect_ok for allowed
# values
def redirect_ok= follow
@agent.redirect_ok = follow
end
alias follow_redirect= redirect_ok=
##
# Maximum number of redirections to follow
def redirection_limit
@agent.redirection_limit
end
##
# Sets the maximum number of redirections to follow to +limit+
def redirection_limit= limit
@agent.redirection_limit = limit
end
##
# Resolve the full path of a link / uri
def resolve link
@agent.resolve link
end
##
# A hash of custom request headers that will be sent on every request
def request_headers
@agent.request_headers
end
##
# Replaces the custom request headers that will be sent on every request
# with +request_headers+
def request_headers= request_headers
@agent.request_headers = request_headers
end
##
# Retry POST and other non-idempotent requests. See RFC 2616 9.1.2.
def retry_change_requests
@agent.retry_change_requests
end
##
# When setting +retry_change_requests+ to true you are stating that, for all
# the URLs you access with mechanize, making POST and other non-idempotent
# requests is safe and will not cause data duplication or other harmful
# results.
#
# If you are experiencing "too many connection resets" errors you should
# instead investigate reducing the idle_timeout or disabling keep_alive
# connections.
def retry_change_requests= retry_change_requests
@agent.retry_change_requests = retry_change_requests
end
##
# Will /robots.txt files be obeyed?
def robots
@agent.robots
end
##
# When +enabled+ mechanize will retrieve and obey robots.txt
# files
def robots= enabled
@agent.robots = enabled
end
##
# The handlers for HTTP and other URI protocols.
def scheme_handlers
@agent.scheme_handlers
end
##
# Replaces the URI scheme handler table with +scheme_handlers+
def scheme_handlers= scheme_handlers
@agent.scheme_handlers = scheme_handlers
end
##
# The identification string for the client initiating a web request
def user_agent
@agent.user_agent
end
##
# Sets the User-Agent used by mechanize to +user_agent+. See also
# user_agent_alias
def user_agent= user_agent
@agent.user_agent = user_agent
end
##
# Set the user agent for the Mechanize object based on the given +name+.
#
# See also AGENT_ALIASES
def user_agent_alias= name
self.user_agent = AGENT_ALIASES[name] ||
raise(ArgumentError, "unknown agent alias #{name.inspect}")
end
##
# The value of watch_for_set is passed to pluggable parsers for retrieved
# content
attr_accessor :watch_for_set
# :section: SSL
#
# SSL settings for mechanize. These must be set in the block given to
# Mechanize.new
##
# Path to an OpenSSL server certificate file
def ca_file
@agent.ca_file
end
##
# Sets the certificate file used for SSL connections
def ca_file= ca_file
@agent.ca_file = ca_file
end
##
# An OpenSSL client certificate or the path to a certificate file.
def cert
@agent.certificate
end
##
# Sets the OpenSSL client certificate +cert+ to the given path or
# certificate instance
def cert= cert
@agent.certificate = cert
end
##
# An OpenSSL certificate store for verifying server certificates. This
# defaults to the default certificate store for your system.
#
# If your system does not ship with a default set of certificates you can
# retrieve a copy of the set from Mozilla here:
# http://curl.haxx.se/docs/caextract.html
#
# (Note that this set does not have an HTTPS download option so you may
# wish to use the firefox-db2pem.sh script to extract the certificates
# from a local install to avoid man-in-the-middle attacks.)
#
# After downloading or generating a cacert.pem from the above link you
# can create a certificate store from the pem file like this:
#
# cert_store = OpenSSL::X509::Store.new
# cert_store.add_file 'cacert.pem'
#
# And have mechanize use it with:
#
# agent.cert_store = cert_store
def cert_store
@agent.cert_store
end
##
# Sets the OpenSSL certificate store to +store+.
#
# See also #cert_store
def cert_store= cert_store
@agent.cert_store = cert_store
end
##
# What is this?
#
# Why is it different from #cert?
def certificate # :nodoc:
@agent.certificate
end
##
# An OpenSSL private key or the path to a private key
def key
@agent.private_key
end
##
# Sets the OpenSSL client +key+ to the given path or key instance. If a
# path is given, the path must contain an RSA key file.
def key= key
@agent.private_key = key
end
##
# OpenSSL client key password
def pass
@agent.pass
end
##
# Sets the client key password to +pass+
def pass= pass
@agent.pass = pass
end
##
# SSL version to use.
def ssl_version
@agent.ssl_version
end
##
# Sets the SSL version to use to +version+ without client/server
# negotiation.
def ssl_version= ssl_version
@agent.ssl_version = ssl_version
end
##
# A callback for additional certificate verification. See
# OpenSSL::SSL::SSLContext#verify_callback
#
# The callback can be used for debugging or to ignore errors by always
# returning +true+. Specifying nil uses the default method that was valid
# when the SSLContext was created
def verify_callback
@agent.verify_callback
end
##
# Sets the OpenSSL certificate verification callback
def verify_callback= verify_callback
@agent.verify_callback = verify_callback
end
##
# the OpenSSL server certificate verification method. The default is
# OpenSSL::SSL::VERIFY_PEER and certificate verification uses the default
# system certificates. See also cert_store
def verify_mode
@agent.verify_mode
end
##
# Sets the OpenSSL server certificate verification method.
def verify_mode= verify_mode
@agent.verify_mode = verify_mode
end
# :section: Utilities
attr_reader :agent # :nodoc:
##
# Parses the +body+ of the +response+ from +uri+ using the pluggable parser
# that matches its content type
def parse uri, response, body
content_type = nil
unless response['Content-Type'].nil?
data, = response['Content-Type'].split ';', 2
content_type, = data.downcase.split ',', 2 unless data.nil?
end
parser_klass = @pluggable_parser.parser content_type
unless parser_klass <= Mechanize::Download then
body = case body
when IO, Tempfile, StringIO then
body.read
else
body
end
end
parser_klass.new uri, response, body, response.code do |parser|
parser.mech = self if parser.respond_to? :mech=
parser.watch_for_set = @watch_for_set if
@watch_for_set and parser.respond_to?(:watch_for_set=)
end
end
def pretty_print(q) # :nodoc:
q.object_group(self) {
q.breakable
q.pp cookie_jar
q.breakable
q.pp current_page
}
end
##
# Sets the proxy +address+ at +port+ with an optional +user+ and +password+
def set_proxy address, port, user = nil, password = nil
@proxy_addr = address
@proxy_port = port
@proxy_user = user
@proxy_pass = password
@agent.set_proxy address, port, user, password
end
##
# Clears history and cookies.
def reset
@agent.reset
end
##
# Shuts down this session by clearing browsing state and closing all
# persistent connections.
def shutdown
reset
@agent.shutdown
end
private
##
# Posts +form+ to +uri+
def post_form(uri, form, headers = {})
cur_page = form.page || current_page ||
Page.new
request_data = form.request_data
log.debug("query: #{ request_data.inspect }") if log
headers = {
'Content-Type' => form.enctype,
'Content-Length' => request_data.size.to_s,
}.merge headers
# fetch the page
page = @agent.fetch uri, :post, headers, [request_data], cur_page
add_to_history(page)
page
end
##
# Adds +page+ to the history
def add_to_history(page)
@agent.history.push(page, @agent.resolve(page.uri))
@history_added.call(page) if @history_added
end
end
require 'mechanize/element_not_found_error'
require 'mechanize/response_read_error'
require 'mechanize/chunked_termination_error'
require 'mechanize/content_type_error'
require 'mechanize/cookie'
require 'mechanize/cookie_jar'
require 'mechanize/parser'
require 'mechanize/download'
require 'mechanize/directory_saver'
require 'mechanize/file'
require 'mechanize/file_connection'
require 'mechanize/file_request'
require 'mechanize/file_response'
require 'mechanize/form'
require 'mechanize/history'
require 'mechanize/http'
require 'mechanize/http/agent'
require 'mechanize/http/auth_challenge'
require 'mechanize/http/auth_realm'
require 'mechanize/http/content_disposition_parser'
require 'mechanize/http/www_authenticate_parser'
require 'mechanize/image'
require 'mechanize/page'
require 'mechanize/pluggable_parsers'
require 'mechanize/redirect_limit_reached_error'
require 'mechanize/redirect_not_get_or_head_error'
require 'mechanize/response_code_error'
require 'mechanize/robots_disallowed_error'
require 'mechanize/unauthorized_error'
require 'mechanize/unsupported_scheme_error'
require 'mechanize/util'
mechanize-2.10.1/lib/mechanize/ 0000755 0000041 0000041 00000000000 14645745627 016323 5 ustar www-data www-data mechanize-2.10.1/lib/mechanize/redirect_limit_reached_error.rb 0000644 0000041 0000041 00000000617 14645745627 024537 0 ustar www-data www-data # frozen_string_literal: true
##
# Raised when too many redirects are sent
class Mechanize::RedirectLimitReachedError < Mechanize::Error
attr_reader :page
attr_reader :redirects
attr_reader :response_code
def initialize page, redirects
@page = page
@redirects = redirects
@response_code = page.code
super "Redirect limit of #{redirects} reached"
end
end
mechanize-2.10.1/lib/mechanize/form.rb 0000644 0000041 0000041 00000044614 14645745627 017624 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/element_matcher'
# This class encapsulates a form parsed out of an HTML page. Each type of
# input field available in a form can be accessed through this object.
#
# == Examples
#
# Find a form and print out its fields
#
# form = page.forms.first # => Mechanize::Form
# form.fields.each { |f| puts f.name }
#
# Set the input field 'name' to "Aaron"
#
# form['name'] = 'Aaron'
# puts form['name']
class Mechanize::Form
extend Forwardable
extend Mechanize::ElementMatcher
attr_accessor :method, :action, :name
attr_reader :fields, :buttons, :file_uploads, :radiobuttons, :checkboxes
# Content-Type for form data (i.e. application/x-www-form-urlencoded)
attr_accessor :enctype
# Character encoding of form data (i.e. UTF-8)
attr_accessor :encoding
# When true, character encoding errors will never be never raised on form
# submission. Default is false
attr_accessor :ignore_encoding_error
alias :elements :fields
attr_reader :node
alias form_node node # for backward compatibility
attr_reader :page
def initialize(node, mech = nil, page = nil)
@enctype = node['enctype'] || 'application/x-www-form-urlencoded'
@node = node
@action = Mechanize::Util.html_unescape(node['action'])
@method = (node['method'] || 'GET').upcase
@name = node['name']
@clicked_buttons = []
@page = page
@mech = mech
@encoding = node['accept-charset'] || (page && page.encoding) || nil
@ignore_encoding_error = false
parse
end
# Returns whether or not the form contains a field with +field_name+
def has_field?(field_name)
fields.any? { |f| f.name == field_name }
end
alias :has_key? :has_field?
# Returns whether or not the form contains a field with +value+
def has_value?(value)
fields.any? { |f| f.value == value }
end
# Returns all field names (keys) for this form
def keys
fields.map(&:name)
end
# Returns all field values for this form
def values
fields.map(&:value)
end
# Returns all buttons of type Submit
def submits
@submits ||= buttons.select { |f| f.class == Submit }
end
# Returns all buttons of type Reset
def resets
@resets ||= buttons.select { |f| f.class == Reset }
end
# Returns all fields of type Text
def texts
@texts ||= fields.select { |f| f.class == Text }
end
# Returns all fields of type Hidden
def hiddens
@hiddens ||= fields.select { |f| f.class == Hidden }
end
# Returns all fields of type Textarea
def textareas
@textareas ||= fields.select { |f| f.class == Textarea }
end
# Returns all fields of type Keygen
def keygens
@keygens ||= fields.select { |f| f.class == Keygen }
end
# Returns whether or not the form contains a Submit button named +button_name+
def submit_button?(button_name)
submits.find { |f| f.name == button_name }
end
# Returns whether or not the form contains a Reset button named +button_name+
def reset_button?(button_name)
resets.find { |f| f.name == button_name }
end
# Returns whether or not the form contains a Text field named +field_name+
def text_field?(field_name)
texts.find { |f| f.name == field_name }
end
# Returns whether or not the form contains a Hidden field named +field_name+
def hidden_field?(field_name)
hiddens.find { |f| f.name == field_name }
end
# Returns whether or not the form contains a Textarea named +field_name+
def textarea_field?(field_name)
textareas.find { |f| f.name == field_name }
end
# This method is a shortcut to get form's DOM id.
# Common usage:
# page.form_with(:dom_id => "foorm")
# Note that you can also use +:id+ to get to this method:
# page.form_with(:id => "foorm")
def dom_id
@node['id']
end
# This method is a shortcut to get form's DOM class.
# Common usage:
# page.form_with(:dom_class => "foorm")
# Note that you can also use +:class+ to get to this method:
# page.form_with(:class => "foorm")
# However, attribute values are compared literally as string, so
# form_with(class: "a") does not match a form with class="a b".
# Use form_with(css: "form.a") instead.
def dom_class
@node['class']
end
##
# :method: search
#
# Shorthand for +node.search+.
#
# See Nokogiri::XML::Node#search for details.
##
# :method: css
#
# Shorthand for +node.css+.
#
# See also Nokogiri::XML::Node#css for details.
##
# :method: xpath
#
# Shorthand for +node.xpath+.
#
# See also Nokogiri::XML::Node#xpath for details.
##
# :method: at
#
# Shorthand for +node.at+.
#
# See also Nokogiri::XML::Node#at for details.
##
# :method: at_css
#
# Shorthand for +node.at_css+.
#
# See also Nokogiri::XML::Node#at_css for details.
##
# :method: at_xpath
#
# Shorthand for +node.at_xpath+.
#
# See also Nokogiri::XML::Node#at_xpath for details.
def_delegators :node, :search, :css, :xpath, :at, :at_css, :at_xpath
# Add a field with +field_name+ and +value+
def add_field!(field_name, value = nil)
fields << Field.new({'name' => field_name}, value)
end
##
# This method sets multiple fields on the form. It takes a list of +fields+
# which are name, value pairs.
#
# If there is more than one field found with the same name, this method will
# set the first one found. If you want to set the value of a duplicate
# field, use a value which is a Hash with the key as the index in to the
# form. The index is zero based.
#
# For example, to set the second field named 'foo', you could do the
# following:
#
# form.set_fields :foo => { 1 => 'bar' }
def set_fields fields = {}
fields.each do |name, v|
case v
when Hash
v.each do |index, value|
self.fields_with(:name => name.to_s)[index].value = value
end
else
value = nil
index = 0
[v].flatten.each do |val|
index = val.to_i if value
value = val unless value
end
self.fields_with(:name => name.to_s)[index].value = value
end
end
end
# Fetch the value of the first input field with the name passed in. Example:
# puts form['name']
def [](field_name)
f = field(field_name)
f && f.value
end
# Set the value of the first input field with the name passed in. Example:
# form['name'] = 'Aaron'
def []=(field_name, value)
f = field(field_name)
if f
f.value = value
else
add_field!(field_name, value)
end
end
# Treat form fields like accessors.
def method_missing(meth, *args)
(method = meth.to_s).chomp!('=')
if field(method)
return field(method).value if args.empty?
return field(method).value = args[0]
end
super
end
# Submit the form. Does not include the +button+ as a form parameter.
# Use +click_button+ or provide button as a parameter.
def submit button = nil, headers = {}
@mech.submit(self, button, headers)
end
# Submit form using +button+. Defaults
# to the first button.
def click_button(button = buttons.first)
submit(button)
end
# This method is sub-method of build_query.
# It converts charset of query value of fields into expected one.
def proc_query(field)
return unless field.query_value
field.query_value.map{|(name, val)|
[from_native_charset(name), from_native_charset(val.to_s)]
}
end
private :proc_query
def from_native_charset str
Mechanize::Util.from_native_charset(str, encoding, @ignore_encoding_error,
@mech && @mech.log)
end
private :from_native_charset
# This method builds an array of arrays that represent the query
# parameters to be used with this form. The return value can then
# be used to create a query string for this form.
def build_query(buttons = [])
query = []
@mech.log.info("form encoding: #{encoding}") if @mech && @mech.log
save_hash_field_order
successful_controls = []
(fields + checkboxes).reject do |f|
f.node["disabled"] || f.node["name"] == ""
end.sort.each do |f|
case f
when Mechanize::Form::CheckBox
if f.checked
successful_controls << f
end
when Mechanize::Form::Field
successful_controls << f
end
end
radio_groups = {}
radiobuttons.each do |f|
fname = from_native_charset(f.name)
radio_groups[fname] ||= []
radio_groups[fname] << f
end
# take one radio button from each group
radio_groups.each_value do |g|
checked = g.select(&:checked)
if checked.uniq.size > 1 then
values = checked.map(&:value).join(', ').inspect
name = checked.first.name.inspect
raise Mechanize::Error,
"radiobuttons #{values} are checked in the #{name} group, " \
"only one is allowed"
else
successful_controls << checked.first unless checked.empty?
end
end
@clicked_buttons.each { |b|
successful_controls << b
}
successful_controls.sort.each do |ctrl| # DOM order
qval = proc_query(ctrl)
query.push(*qval)
end
query
end
# This method adds an index to all fields that have Hash nodes. This
# enables field sorting to maintain order.
def save_hash_field_order
index = 0
fields.each do |field|
if Hash === field.node
field.index = index
index += 1
end
end
end
# This method adds a button to the query. If the form needs to be
# submitted with multiple buttons, pass each button to this method.
def add_button_to_query(button)
unless button.node.document == @node.document then
message =
"#{button.inspect} does not belong to the same page as " \
"the form #{@name.inspect} in #{@page.uri}"
raise ArgumentError, message
end
@clicked_buttons << button
end
# This method allows the same form to be submitted second time
# with the different submit button being clicked.
def reset
# In the future, should add more functionality here to reset the form values to their defaults.
@clicked_buttons = []
end
CRLF = "\r\n".freeze
# This method calculates the request data to be sent back to the server
# for this form, depending on if this is a regular post, get, or a
# multi-part post,
def request_data
query_params = build_query()
case @enctype.downcase
when /^multipart\/form-data/
boundary = rand_string(20)
@enctype = "multipart/form-data; boundary=#{boundary}"
delimiter = "--#{boundary}\r\n"
data = ::String.new
query_params.each do |k,v|
if k
data << delimiter
param_to_multipart(k, v, data)
end
end
@file_uploads.each do |f|
data << delimiter
file_to_multipart(f, data)
end
data << "--#{boundary}--\r\n"
else
Mechanize::Util.build_query_string(query_params)
end
end
# Removes all fields with name +field_name+.
def delete_field!(field_name)
@fields.delete_if{ |f| f.name == field_name}
end
##
# :method: field_with(criteria)
#
# Find one field that matches +criteria+
# Example:
# form.field_with(:id => "exact_field_id").value = 'hello'
##
# :method: field_with!(criteria)
#
# Same as +field_with+ but raises an ElementNotFoundError if no field matches
# +criteria+
##
# :method: fields_with(criteria)
#
# Find all fields that match +criteria+
# Example:
# form.fields_with(:value => /foo/).each do |field|
# field.value = 'hello!'
# end
elements_with :field
##
# :method: button_with(criteria)
#
# Find one button that matches +criteria+
# Example:
# form.button_with(:value => /submit/).value = 'hello'
##
# :method: button_with!(criteria)
#
# Same as +button_with+ but raises an ElementNotFoundError if no button
# matches +criteria+
##
# :method: buttons_with(criteria)
#
# Find all buttons that match +criteria+
# Example:
# form.buttons_with(:value => /submit/).each do |button|
# button.value = 'hello!'
# end
elements_with :button
##
# :method: file_upload_with(criteria)
#
# Find one file upload field that matches +criteria+
# Example:
# form.file_upload_with(:file_name => /picture/).value = 'foo'
##
# :method: file_upload_with!(criteria)
#
# Same as +file_upload_with+ but raises an ElementNotFoundError if no button
# matches +criteria+
##
# :method: file_uploads_with(criteria)
#
# Find all file upload fields that match +criteria+
# Example:
# form.file_uploads_with(:file_name => /picutre/).each do |field|
# field.value = 'foo!'
# end
elements_with :file_upload
##
# :method: radiobutton_with(criteria)
#
# Find one radio button that matches +criteria+
# Example:
# form.radiobutton_with(:name => /woo/).check
##
# :method: radiobutton_with!(criteria)
#
# Same as +radiobutton_with+ but raises an ElementNotFoundError if no button
# matches +criteria+
##
# :method: radiobuttons_with(criteria)
#
# Find all radio buttons that match +criteria+
# Example:
# form.radiobuttons_with(:name => /woo/).each do |field|
# field.check
# end
elements_with :radiobutton
##
# :method: checkbox_with(criteria)
#
# Find one checkbox that matches +criteria+
# Example:
# form.checkbox_with(:name => /woo/).check
##
# :method: checkbox_with!(criteria)
#
# Same as +checkbox_with+ but raises an ElementNotFoundError if no button
# matches +criteria+
##
# :method: checkboxes_with(criteria)
#
# Find all checkboxes that match +criteria+
# Example:
# form.checkboxes_with(:name => /woo/).each do |field|
# field.check
# end
elements_with :checkbox, :checkboxes
def pretty_print(q) # :nodoc:
q.object_group(self) {
q.breakable; q.group(1, '{name', '}') { q.breakable; q.pp name }
q.breakable; q.group(1, '{method', '}') { q.breakable; q.pp method }
q.breakable; q.group(1, '{action', '}') { q.breakable; q.pp action }
q.breakable; q.group(1, '{fields', '}') {
fields.each do |field|
q.breakable
q.pp field
end
}
q.breakable; q.group(1, '{radiobuttons', '}') {
radiobuttons.each { |b| q.breakable; q.pp b }
}
q.breakable; q.group(1, '{checkboxes', '}') {
checkboxes.each { |b| q.breakable; q.pp b }
}
q.breakable; q.group(1, '{file_uploads', '}') {
file_uploads.each { |b| q.breakable; q.pp b }
}
q.breakable; q.group(1, '{buttons', '}') {
buttons.each { |b| q.breakable; q.pp b }
}
}
end
alias inspect pretty_inspect # :nodoc:
private
def parse
@fields = []
@buttons = []
@file_uploads = []
@radiobuttons = []
@checkboxes = []
# Find all input tags
@node.search('input').each do |node|
type = (node['type'] || 'text').downcase
name = node['name']
next if name.nil? && !%w[submit button image].include?(type)
case type
when 'radio'
@radiobuttons << RadioButton.new(node, self)
when 'checkbox'
@checkboxes << CheckBox.new(node, self)
when 'file'
@file_uploads << FileUpload.new(node, nil)
when 'submit'
@buttons << Submit.new(node)
when 'button'
@buttons << Button.new(node)
when 'reset'
@buttons << Reset.new(node)
when 'image'
@buttons << ImageButton.new(node)
when 'hidden'
@fields << Hidden.new(node, node['value'] || '')
when 'text'
@fields << Text.new(node, node['value'] || '')
when 'textarea'
@fields << Textarea.new(node, node['value'] || '')
else
@fields << Field.new(node, node['value'] || '')
end
end
# Find all textarea tags
@node.search('textarea').each do |node|
next unless node['name']
@fields << Textarea.new(node, node.inner_text)
end
# Find all select tags
@node.search('select').each do |node|
next unless node['name']
if node.has_attribute? 'multiple'
@fields << MultiSelectList.new(node)
else
@fields << SelectList.new(node)
end
end
# Find all submit button tags
# FIXME: what can I do with the reset buttons?
@node.search('button').each do |node|
type = (node['type'] || 'submit').downcase
next if type == 'reset'
@buttons << Button.new(node)
end
# Find all keygen tags
@node.search('keygen').each do |node|
@fields << Keygen.new(node, node['value'] || '')
end
end
def rand_string(len = 10)
chars = ("a".."z").to_a + ("A".."Z").to_a
string = ::String.new
1.upto(len) { |i| string << chars[rand(chars.size-1)] }
string
end
def mime_value_quote(str)
str.b.gsub(/(["\r\\])/, '\\\\\1')
end
def param_to_multipart(name, value, buf = ::String.new)
buf <<
"Content-Disposition: form-data; name=\"".freeze <<
mime_value_quote(name) <<
"\"\r\n\r\n".freeze <<
value.b <<
CRLF
end
def file_to_multipart(file, buf = ::String.new)
file_name = file.file_name ? ::File.basename(file.file_name) : ''
body = buf <<
"Content-Disposition: form-data; name=\"".freeze <<
mime_value_quote(file.name) <<
"\"; filename=\"".freeze <<
mime_value_quote(file_name) <<
"\"\r\nContent-Transfer-Encoding: binary\r\n".freeze
if file.file_data.nil? and file.file_name
file.file_data = File.binread(file.file_name)
file.mime_type =
WEBrick::HTTPUtils.mime_type(file.file_name,
WEBrick::HTTPUtils::DefaultMimeTypes)
end
if file.mime_type
body << "Content-Type: ".freeze << file.mime_type << CRLF
end
body << CRLF
if file_data = file.file_data
if file_data.respond_to? :read
body << file_data.read.force_encoding(Encoding::ASCII_8BIT)
else
body << file_data.b
end
end
body << CRLF
end
end
require 'mechanize/form/field'
require 'mechanize/form/button'
require 'mechanize/form/hidden'
require 'mechanize/form/text'
require 'mechanize/form/textarea'
require 'mechanize/form/submit'
require 'mechanize/form/reset'
require 'mechanize/form/file_upload'
require 'mechanize/form/keygen'
require 'mechanize/form/image_button'
require 'mechanize/form/multi_select_list'
require 'mechanize/form/option'
require 'mechanize/form/radio_button'
require 'mechanize/form/check_box'
require 'mechanize/form/select_list'
mechanize-2.10.1/lib/mechanize/cookie.rb 0000644 0000041 0000041 00000002621 14645745627 020122 0 ustar www-data www-data # frozen_string_literal: true
warn 'mechanize/cookie will be deprecated. Please migrate to the http-cookie APIs.' if $VERBOSE
require 'http/cookie'
class Mechanize
module CookieDeprecated
def __deprecated__(to = nil)
$VERBOSE or return
method = caller_locations(1,1).first.base_label
to ||= method
case self
when Class
lname = name[/[^:]+$/]
klass = 'Mechanize::%s' % lname
this = '%s.%s' % [klass, method]
that = 'HTTP::%s.%s' % [lname, to]
else
lname = self.class.name[/[^:]+$/]
klass = 'Mechanize::%s' % lname
this = '%s#%s' % [klass, method]
that = 'HTTP::%s#%s' % [lname, to]
end
warn '%s: The call of %s needs to be fixed to follow the new API (%s).' % [caller_locations(2,1).first, this, that]
end
private :__deprecated__
end
module CookieCMethods
include CookieDeprecated
def parse(arg1, arg2, arg3 = nil, &block)
if arg1.is_a?(URI)
__deprecated__
return [] if arg2.nil?
super(arg2, arg1, { :logger => arg3 })
else
super
end
end
end
module CookieIMethods
include CookieDeprecated
def set_domain(domain)
__deprecated__ :domain=
@domain = domain
end
end
Cookie = ::HTTP::Cookie
class Cookie
prepend CookieIMethods
class << self
prepend CookieCMethods
end
end
end
mechanize-2.10.1/lib/mechanize/prependable.rb 0000644 0000041 0000041 00000004534 14645745627 021137 0 ustar www-data www-data # frozen_string_literal: true
# Fake implementation of prepend(), which does not support overriding
# inherited methods nor methods that are formerly overridden by
# another invocation of prepend().
#
# Here's what .prepend() does:
#
# - Create an anonymous stub module (hereinafter ) and define
# # that calls #_without_ for each
# instance method of .
#
# - Rename # to #_without_ for each
# instance method of .
#
# - Include and into in that order.
#
# This way, a call of # is dispatched to
# , which may call super which is dispatched to
# #, which finally calls
# #_without_ which is used to be called
# #.
#
# Usage:
#
# class Mechanize
# # module with methods that overrides those of X
# module Y
# end
#
# unless X.respond_to?(:prepend, true)
# require 'mechanize/prependable'
# X.extend(Prependable)
# end
#
# class X
# prepend Y
# end
# end
module Mechanize::Prependable
def prepend(mod)
stub = Module.new
mod_id = (mod.name || 'Module__%d' % mod.object_id).gsub(/::/, '__')
mod.instance_methods.each { |name|
method_defined?(name) or next
original = instance_method(name)
if original.owner != self
warn "%s cannot override an inherited method: %s(%s)#%s" % [
__method__, self, original.owner, name
]
next
end
name = name.to_s
name_without = name.sub(/(?=[?!=]?\z)/) { '_without_%s' % mod_id }
arity = original.arity
arglist = (
if arity >= 0
(1..arity).map { |i| 'x%d' % i }
else
(1..(-arity - 1)).map { |i| 'x%d' % i } << '*a'
end << '&b'
).join(', ')
if name.end_with?('=')
stub.module_eval %{
def #{name}(#{arglist})
__send__(:#{name_without}, #{arglist})
end
}
else
stub.module_eval %{
def #{name}(#{arglist})
#{name_without}(#{arglist})
end
}
end
module_eval {
alias_method name_without, name
remove_method name
}
}
include(mod, stub)
end
private :prepend
end
mechanize-2.10.1/lib/mechanize/unsupported_scheme_error.rb 0000644 0000041 0000041 00000000307 14645745627 023775 0 ustar www-data www-data # frozen_string_literal: true
class Mechanize::UnsupportedSchemeError < Mechanize::Error
attr_accessor :scheme, :uri
def initialize(scheme, uri)
@scheme = scheme
@uri = uri
end
end
mechanize-2.10.1/lib/mechanize/test_case/ 0000755 0000041 0000041 00000000000 14645745627 020275 5 ustar www-data www-data mechanize-2.10.1/lib/mechanize/test_case/referer_servlet.rb 0000644 0000041 0000041 00000000460 14645745627 024020 0 ustar www-data www-data # frozen_string_literal: true
class RefererServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
res['Content-Type'] = "text/html"
res.body = req['Referer'] || ''
end
def do_POST(req, res)
res['Content-Type'] = "text/html"
res.body = req['Referer'] || ''
end
end
mechanize-2.10.1/lib/mechanize/test_case/many_cookies_servlet.rb 0000644 0000041 0000041 00000002154 14645745627 025050 0 ustar www-data www-data # frozen_string_literal: true
class ManyCookiesServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
name_cookie = WEBrick::Cookie.new("name", "Aaron")
name_cookie.path = "/"
name_cookie.expires = Time.now + 86400
res.cookies << name_cookie
res.cookies << name_cookie
res.cookies << name_cookie
res.cookies << name_cookie
expired_cookie = WEBrick::Cookie.new("expired", "doh")
expired_cookie.path = "/"
expired_cookie.expires = Time.now - 86400
res.cookies << expired_cookie
different_path_cookie = WEBrick::Cookie.new("a_path", "some_path")
different_path_cookie.path = "/some_path"
different_path_cookie.expires = Time.now + 86400
res.cookies << different_path_cookie
no_path_cookie = WEBrick::Cookie.new("no_path", "no_path")
no_path_cookie.expires = Time.now + 86400
res.cookies << no_path_cookie
no_exp_path_cookie = WEBrick::Cookie.new("no_expires", "nope")
no_exp_path_cookie.path = "/"
res.cookies << no_exp_path_cookie
res['Content-Type'] = "text/html"
res.body = "hello"
end
end
mechanize-2.10.1/lib/mechanize/test_case/content_type_servlet.rb 0000644 0000041 0000041 00000000362 14645745627 025102 0 ustar www-data www-data # frozen_string_literal: true
class ContentTypeServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
ct = req.query['ct'] || "text/html; charset=utf-8"
res['Content-Type'] = ct
res.body = "Hello World"
end
end
mechanize-2.10.1/lib/mechanize/test_case/servlets.rb 0000644 0000041 0000041 00000005333 14645745627 022475 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case/bad_chunking_servlet'
require 'mechanize/test_case/basic_auth_servlet'
require 'mechanize/test_case/content_type_servlet'
require 'mechanize/test_case/digest_auth_servlet'
require 'mechanize/test_case/file_upload_servlet'
require 'mechanize/test_case/form_servlet'
require 'mechanize/test_case/gzip_servlet'
require 'mechanize/test_case/header_servlet'
require 'mechanize/test_case/http_refresh_servlet'
require 'mechanize/test_case/infinite_redirect_servlet'
require 'mechanize/test_case/infinite_refresh_servlet'
require 'mechanize/test_case/many_cookies_as_string_servlet'
require 'mechanize/test_case/many_cookies_servlet'
require 'mechanize/test_case/modified_since_servlet'
require 'mechanize/test_case/ntlm_servlet'
require 'mechanize/test_case/one_cookie_no_spaces_servlet'
require 'mechanize/test_case/one_cookie_servlet'
require 'mechanize/test_case/quoted_value_cookie_servlet'
require 'mechanize/test_case/redirect_servlet'
require 'mechanize/test_case/referer_servlet'
require 'mechanize/test_case/refresh_with_empty_url'
require 'mechanize/test_case/refresh_without_url'
require 'mechanize/test_case/response_code_servlet'
require 'mechanize/test_case/robots_txt_servlet'
require 'mechanize/test_case/send_cookies_servlet'
require 'mechanize/test_case/verb_servlet'
MECHANIZE_TEST_CASE_SERVLETS = {
'/bad_chunking' => BadChunkingServlet,
'/basic_auth' => BasicAuthServlet,
'/content_type_test' => ContentTypeServlet,
'/digest_auth' => DigestAuthServlet,
'/file_upload' => FileUploadServlet,
'/form post' => FormServlet,
'/form_post' => FormServlet,
'/gzip' => GzipServlet,
'/http_headers' => HeaderServlet,
'/http_refresh' => HttpRefreshServlet,
'/if_modified_since' => ModifiedSinceServlet,
'/infinite_redirect' => InfiniteRedirectServlet,
'/infinite_refresh' => InfiniteRefreshServlet,
'/many_cookies' => ManyCookiesServlet,
'/many_cookies_as_string' => ManyCookiesAsStringServlet,
'/ntlm' => NTLMServlet,
'/one_cookie' => OneCookieServlet,
'/one_cookie_no_space' => OneCookieNoSpacesServlet,
'/quoted_value_cookie' => QuotedValueCookieServlet,
'/redirect' => RedirectServlet,
'/referer' => RefererServlet,
'/refresh_with_empty_url' => RefreshWithEmptyUrl,
'/refresh_without_url' => RefreshWithoutUrl,
'/response_code' => ResponseCodeServlet,
'/robots.txt' => RobotsTxtServlet,
'/robots_txt' => RobotsTxtServlet,
'/send_cookies' => SendCookiesServlet,
'/verb' => VerbServlet,
}
mechanize-2.10.1/lib/mechanize/test_case/verb_servlet.rb 0000644 0000041 0000041 00000000410 14645745627 023317 0 ustar www-data www-data # frozen_string_literal: true
class VerbServlet < WEBrick::HTTPServlet::AbstractServlet
%w[HEAD GET POST PUT DELETE].each do |verb|
define_method "do_#{verb}" do |req, res|
res.header['X-Request-Method'] = verb
res.body = verb
end
end
end
mechanize-2.10.1/lib/mechanize/test_case/digest_auth_servlet.rb 0000644 0000041 0000041 00000001424 14645745627 024667 0 ustar www-data www-data # frozen_string_literal: true
require 'logger'
class DigestAuthServlet < WEBrick::HTTPServlet::AbstractServlet
htpd = nil
Tempfile.open 'digest.htpasswd' do |io|
htpd = WEBrick::HTTPAuth::Htdigest.new(io.path)
htpd.set_passwd('Blah', 'user', 'pass')
end
@@authenticator = WEBrick::HTTPAuth::DigestAuth.new({
:UserDB => htpd,
:Realm => 'Blah',
:Algorithm => 'MD5',
:Logger => Logger.new(nil)
})
def do_GET req, res
def req.request_time; Time.now; end
def req.request_uri; '/digest_auth'; end
def req.request_method; 'GET'; end
begin
@@authenticator.authenticate req, res
res.body = 'You are authenticated'
rescue WEBrick::HTTPStatus::Unauthorized
res.status = 401
end
end
alias :do_POST :do_GET
end
mechanize-2.10.1/lib/mechanize/test_case/robots_txt_servlet.rb 0000644 0000041 0000041 00000000575 14645745627 024604 0 ustar www-data www-data # frozen_string_literal: true
class RobotsTxtServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
if /301/ === req['Host'] && req.path == '/robots.txt'
res['Location'] = 'http://301/robots_txt'
res.code = 301
else
res['Content-Type'] = 'text/plain'
res.body = <<-'EOF'
User-Agent: *
Disallow: /norobots
EOF
end
end
end
mechanize-2.10.1/lib/mechanize/test_case/http_refresh_servlet.rb 0000644 0000041 0000041 00000000531 14645745627 025062 0 ustar www-data www-data # frozen_string_literal: true
class HttpRefreshServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
res['Content-Type'] = req.query['ct'] || "text/html"
refresh_time = req.query['refresh_time'] || 0
refresh_url = req.query['refresh_url'] || '/'
res['Refresh'] = " #{refresh_time};url=#{refresh_url}";
end
end
mechanize-2.10.1/lib/mechanize/test_case/server.rb 0000644 0000041 0000041 00000001713 14645745627 022132 0 ustar www-data www-data # frozen_string_literal: true
require 'webrick'
require 'mechanize/test_case/servlets'
server = WEBrick::HTTPServer.new :Port => 8000
server.mount_proc '/' do |req, res|
res.content_type = 'text/html'
servlets = MECHANIZE_TEST_CASE_SERVLETS.map do |path, servlet|
"
#{servlet}
#{path}"
end.join "\n"
res.body = <<-BODY
Mechanize Test Case Servlets
This server allows you to test various mechanize behavior against other
HTTP clients. Some endpoints may require headers be set to have a reasonable
function, or may respond diffently to POST vs GET requests. Please see the
servlet implementation and mechanize tests for further details.
Here are the servlet endpoints available:
#{servlets}
BODY
end
MECHANIZE_TEST_CASE_SERVLETS.each do |path, servlet|
server.mount path, servlet
end
trap 'INT' do server.shutdown end
trap 'TERM' do server.shutdown end
server.start
mechanize-2.10.1/lib/mechanize/test_case/gzip_servlet.rb 0000644 0000041 0000041 00000001470 14645745627 023341 0 ustar www-data www-data # frozen_string_literal: true
require 'stringio'
require 'zlib'
class GzipServlet < WEBrick::HTTPServlet::AbstractServlet
TEST_DIR = File.expand_path '../../../../test', __FILE__
def do_GET(req, res)
if req['Accept-Encoding'] !~ /gzip/ then
res.code = 400
res.body = 'Content-Encoding: gzip is not supported by your user-agent'
return
end
if name = req.query['file'] then
::File.open("#{TEST_DIR}/htdocs/#{name}") do |io|
string = String.new
zipped = StringIO.new string, 'w'
Zlib::GzipWriter.wrap zipped do |gz|
gz.write io.read
end
res.body = string
end
else
res.body = String.new
end
res['Content-Encoding'] = req['X-ResponseContentEncoding'] || 'gzip'
res['Content-Type'] = "text/html"
end
end
mechanize-2.10.1/lib/mechanize/test_case/.document 0000644 0000041 0000041 00000000040 14645745627 022106 0 ustar www-data www-data # Don't document this directory
mechanize-2.10.1/lib/mechanize/test_case/infinite_refresh_servlet.rb 0000644 0000041 0000041 00000000660 14645745627 025713 0 ustar www-data www-data # frozen_string_literal: true
class InfiniteRefreshServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
address = "#{req.host}:#{req.port}"
res['Content-Type'] = req.query['ct'] || "text/html"
res.status = req.query['code'] ? req.query['code'].to_i : '302'
number = req.query['q'] ? req.query['q'].to_i : 0
res['Refresh'] = "0;url=http://#{address}/infinite_refresh?q=#{number + 1}";
end
end
mechanize-2.10.1/lib/mechanize/test_case/header_servlet.rb 0000644 0000041 0000041 00000000426 14645745627 023620 0 ustar www-data www-data # frozen_string_literal: true
class HeaderServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
res.content_type = "text/plain"
req.query.each do |x,y|
res[x] = y
end
req.each do |k, v|
res.body << "#{k}|#{v}\n"
end
end
end
mechanize-2.10.1/lib/mechanize/test_case/quoted_value_cookie_servlet.rb 0000644 0000041 0000041 00000000554 14645745627 026420 0 ustar www-data www-data # frozen_string_literal: true
class QuotedValueCookieServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
cookie = WEBrick::Cookie.new("quoted", "\"value\"")
cookie.path = "/"
cookie.expires = Time.now + 86400
res.cookies << cookie
res['Content-Type'] = "text/html"
res.body = "hello"
end
end
mechanize-2.10.1/lib/mechanize/test_case/one_cookie_servlet.rb 0000644 0000041 0000041 00000000533 14645745627 024501 0 ustar www-data www-data # frozen_string_literal: true
class OneCookieServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
cookie = WEBrick::Cookie.new("foo", "bar")
cookie.path = "/"
cookie.expires = Time.now + 86400
res.cookies << cookie
res['Content-Type'] = "text/html"
res.body = "hello"
end
end
mechanize-2.10.1/lib/mechanize/test_case/form_servlet.rb 0000644 0000041 0000041 00000001760 14645745627 023335 0 ustar www-data www-data # frozen_string_literal: true
class FormServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
res.content_type = 'text/html'
query = []
req.query.each_key { |k|
key = WEBrick::HTTPUtils.unescape k
req.query[k].each_data { |data|
value = WEBrick::HTTPUtils.unescape data
query << "
BODY
end
end
mechanize-2.10.1/lib/mechanize/test_case/modified_since_servlet.rb 0000644 0000041 0000041 00000001050 14645745627 025323 0 ustar www-data www-data # frozen_string_literal: true
class ModifiedSinceServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
s_time = 'Fri, 04 May 2001 00:00:38 GMT'
my_time = Time.parse(s_time)
if req['If-Modified-Since']
your_time = Time.parse(req['If-Modified-Since'])
if my_time > your_time
res.body = 'This page was updated since you requested'
else
res.status = 304
end
else
res.body = 'You did not send an If-Modified-Since header'
end
res['Last-Modified'] = s_time
end
end
mechanize-2.10.1/lib/mechanize/test_case/response_code_servlet.rb 0000644 0000041 0000041 00000000620 14645745627 025214 0 ustar www-data www-data # frozen_string_literal: true
class ResponseCodeServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
res['Content-Type'] = req.query['ct'] || "text/html"
if req.query['code']
code = req.query['code'].to_i
case code
when 300, 301, 302, 303, 304, 305, 307
res['Location'] = "/index.html"
end
res.status = code
else
end
end
end
mechanize-2.10.1/lib/mechanize/test_case/many_cookies_as_string_servlet.rb 0000644 0000041 0000041 00000002305 14645745627 027117 0 ustar www-data www-data # frozen_string_literal: true
class ManyCookiesAsStringServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
cookies = []
name_cookie = WEBrick::Cookie.new("name", "Aaron")
name_cookie.path = "/"
name_cookie.expires = Time.now + 86400
name_cookie.domain = 'localhost'
cookies << name_cookie
cookies << name_cookie
cookies << name_cookie
cookies << "#{name_cookie}; HttpOnly"
expired_cookie = WEBrick::Cookie.new("expired", "doh")
expired_cookie.path = "/"
expired_cookie.expires = Time.now - 86400
cookies << expired_cookie
different_path_cookie = WEBrick::Cookie.new("a_path", "some_path")
different_path_cookie.path = "/some_path"
different_path_cookie.expires = Time.now + 86400
cookies << different_path_cookie
no_path_cookie = WEBrick::Cookie.new("no_path", "no_path")
no_path_cookie.expires = Time.now + 86400
cookies << no_path_cookie
no_exp_path_cookie = WEBrick::Cookie.new("no_expires", "nope")
no_exp_path_cookie.path = "/"
cookies << no_exp_path_cookie
res['Set-Cookie'] = cookies.join(', ')
res['Content-Type'] = "text/html"
res.body = "hello"
end
end
mechanize-2.10.1/lib/mechanize/http.rb 0000644 0000041 0000041 00000000342 14645745627 017626 0 ustar www-data www-data # frozen_string_literal: true
##
# Mechanize::HTTP contains classes for communicated with HTTP servers. All
# API under this namespace is considered private and is subject to change at
# any time.
class Mechanize::HTTP
end
mechanize-2.10.1/lib/mechanize/history.rb 0000644 0000041 0000041 00000002404 14645745627 020351 0 ustar www-data www-data # frozen_string_literal: true
##
# This class manages history for your mechanize object.
class Mechanize::History < Array
attr_accessor :max_size
def initialize(max_size = nil)
@max_size = max_size
@history_index = {}
end
def initialize_copy(orig)
super
@history_index = orig.instance_variable_get(:@history_index).dup
end
def inspect # :nodoc:
uris = map(&:uri).join ', '
"[#{uris}]"
end
def push(page, uri = nil)
super page
index = uri ? uri : page.uri
@history_index[index.to_s] = page
shift while length > @max_size if @max_size
self
end
alias :<< :push
def visited? uri
page = @history_index[uri.to_s]
return page if page # HACK
uri = uri.dup
uri.path = '/' if uri.path.empty?
@history_index[uri.to_s]
end
alias visited_page visited?
def clear
@history_index.clear
super
end
def shift
return nil if length == 0
page = self[0]
self[0] = nil
super
remove_from_index(page)
page
end
def pop
return nil if length == 0
page = super
remove_from_index(page)
page
end
private
def remove_from_index(page)
@history_index.each do |k,v|
@history_index.delete(k) if v == page
end
end
end
mechanize-2.10.1/lib/mechanize/element_not_found_error.rb 0000644 0000041 0000041 00000000662 14645745627 023571 0 ustar www-data www-data # frozen_string_literal: true
##
# Raised when an an element was not found on the Page
class Mechanize::ElementNotFoundError < Mechanize::Error
attr_reader :source
attr_reader :element
attr_reader :conditions
def initialize source, element, conditions
@source = source
@element = element
@conditions = conditions
super "Element #{element} with conditions #{conditions} was not found"
end
end
mechanize-2.10.1/lib/mechanize/response_read_error.rb 0000644 0000041 0000041 00000001656 14645745627 022722 0 ustar www-data www-data # frozen_string_literal: true
##
# Raised when Mechanize encounters an error while reading the response body
# from the server. Contains the response headers and the response body up to
# the error along with the initial error.
class Mechanize::ResponseReadError < Mechanize::Error
attr_reader :body_io
attr_reader :error
attr_reader :mechanize
attr_reader :response
attr_reader :uri
##
# Creates a new ResponseReadError with the +error+ raised, the +response+
# and the +body_io+ for content read so far.
def initialize error, response, body_io, uri, mechanize
@body_io = body_io
@error = error
@mechanize = mechanize
@response = response
@uri = uri
end
##
# Converts this error into a Page, File, etc. based on the content-type
def force_parse
@mechanize.parse @uri, @response, @body_io
end
def message # :nodoc:
"#{@error.message} (#{self.class})"
end
end
mechanize-2.10.1/lib/mechanize/pluggable_parsers.rb 0000644 0000041 0000041 00000011544 14645745627 022356 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/file'
require 'mechanize/file_saver'
require 'mechanize/page'
require 'mechanize/xml_file'
require 'mime/types'
##
# Mechanize allows different parsers for different content types. Mechanize
# uses PluggableParser to determine which parser to use for any content type.
# To use your own parser or to change the default parsers, register them with
# this class through Mechanize#pluggable_parser.
#
# The default parser for unregistered content types is Mechanize::File.
#
# The module Mechanize::Parser provides basic functionality for any content
# type, so you may use it in custom parsers you write. For small files you
# wish to perform in-memory operations on, you should subclass
# Mechanize::File. For large files you should subclass Mechanize::Download as
# the content is only loaded into memory in small chunks.
#
# When writing your own pluggable parser, be sure to provide a method #body
# that returns a String containing the response body for compatibility with
# Mechanize#get_file.
#
# == Example
#
# To create your own parser, just create a class that takes four parameters in
# the constructor. Here is an example of registering a parser that handles
# CSV files:
#
# require 'csv'
#
# class CSVParser < Mechanize::File
# attr_reader :csv
#
# def initialize uri = nil, response = nil, body = nil, code = nil
# super uri, response, body, code
# @csv = CSV.parse body
# end
# end
#
# agent = Mechanize.new
# agent.pluggable_parser.csv = CSVParser
# agent.get('http://example.com/test.csv') # => CSVParser
#
# Now any response with a content type of 'text/csv' will initialize a
# CSVParser and return that object to the caller.
#
# To register a parser for a content type that Mechanize does not know about,
# use the hash syntax:
#
# agent.pluggable_parser['text/something'] = SomeClass
#
# To set the default parser, use #default:
#
# agent.pluggable_parser.default = Mechanize::Download
#
# Now all unknown content types will be saved to disk and not loaded into
# memory.
class Mechanize::PluggableParser
CONTENT_TYPES = {
:html => 'text/html',
:wap => 'application/vnd.wap.xhtml+xml',
:xhtml => 'application/xhtml+xml',
:pdf => 'application/pdf',
:csv => 'text/csv',
:xml => ['text/xml', 'application/xml'],
}
InvalidContentTypeError =
if defined?(MIME::Type::InvalidContentType)
# For mime-types >=2.1
MIME::Type::InvalidContentType
else
# For mime-types <2.1
MIME::InvalidContentType
end
attr_accessor :default
def initialize
@parsers = {
CONTENT_TYPES[:html] => Mechanize::Page,
CONTENT_TYPES[:xhtml] => Mechanize::Page,
CONTENT_TYPES[:wap] => Mechanize::Page,
'image' => Mechanize::Image,
'text/xml' => Mechanize::XmlFile,
'application/xml' => Mechanize::XmlFile,
}
@default = Mechanize::File
end
##
# Returns the parser registered for the given +content_type+
def parser content_type
return default unless content_type
parser = @parsers[content_type]
return parser if parser
mime_type = MIME::Type.new content_type
parser = @parsers[mime_type.to_s] ||
@parsers[mime_type.simplified] ||
# Starting from mime-types 3.0 x-prefix is deprecated as per IANA
(@parsers[MIME::Type.simplified(mime_type.to_s, remove_x_prefix: true)] rescue nil) ||
@parsers[mime_type.media_type] ||
default
rescue InvalidContentTypeError
default
end
def register_parser content_type, klass # :nodoc:
@parsers[content_type] = klass
end
##
# Registers +klass+ as the parser for text/html and application/xhtml+xml
# content
def html=(klass)
register_parser(CONTENT_TYPES[:html], klass)
register_parser(CONTENT_TYPES[:xhtml], klass)
end
##
# Registers +klass+ as the parser for application/xhtml+xml content
def xhtml=(klass)
register_parser(CONTENT_TYPES[:xhtml], klass)
end
##
# Registers +klass+ as the parser for application/pdf content
def pdf=(klass)
register_parser(CONTENT_TYPES[:pdf], klass)
end
##
# Registers +klass+ as the parser for text/csv content
def csv=(klass)
register_parser(CONTENT_TYPES[:csv], klass)
end
##
# Registers +klass+ as the parser for text/xml content
def xml=(klass)
CONTENT_TYPES[:xml].each do |content_type|
register_parser content_type, klass
end
end
##
# Retrieves the parser for +content_type+ content
def [](content_type)
@parsers[content_type]
end
##
# Sets the parser for +content_type+ content to +klass+
#
# The +content_type+ may either be a full MIME type a simplified MIME type
# ('text/x-csv' simplifies to 'text/csv') or a media type like 'image'.
def []= content_type, klass
register_parser content_type, klass
end
end
mechanize-2.10.1/lib/mechanize/directory_saver.rb 0000644 0000041 0000041 00000004033 14645745627 022054 0 ustar www-data www-data # frozen_string_literal: true
##
# Unlike Mechanize::FileSaver, the directory saver places all downloaded files
# in a single pre-specified directory.
#
# You must register the directory to save to before using the directory saver:
#
# agent.pluggable_parser['image'] = \
# Mechanize::DirectorySaver.save_to 'images'
class Mechanize::DirectorySaver < Mechanize::Download
@directory = nil
@options = {}
##
# Creates a DirectorySaver subclass that will save responses to the given
# +directory+. If +options+ includes a +decode_filename+ value set to +true+
# then the downloaded filename will be ran through +CGI.unescape+ before
# being saved. If +options+ includes a +overwrite+ value set to +true+ then
# downloaded file will be overwritten if two files with the same names exist.
def self.save_to directory, options = {}
directory = File.expand_path directory
Class.new self do |klass|
klass.instance_variable_set :@directory, directory
klass.instance_variable_set :@options, options
end
end
##
# The directory downloaded files will be saved to.
def self.directory
@directory
end
##
# True if downloaded files should have their names decoded before saving.
def self.decode_filename?
@options[:decode_filename]
end
##
# Checks if +overwrite+ parameter is set to true
def self.overwrite?
@options[:overwrite]
end
##
# Saves the +body_io+ into the directory specified for this DirectorySaver
# by save_to. The filename is chosen by Mechanize::Parser#extract_filename.
def initialize uri = nil, response = nil, body_io = nil, code = nil
directory = self.class.directory
raise Mechanize::Error,
'no save directory specified - ' \
'use Mechanize::DirectorySaver.save_to ' \
'and register the resulting class' unless directory
super
@filename = CGI.unescape(@filename) if self.class.decode_filename?
path = File.join directory, @filename
if self.class.overwrite?
save! path
else
save path
end
end
end
mechanize-2.10.1/lib/mechanize/image.rb 0000644 0000041 0000041 00000000216 14645745627 017731 0 ustar www-data www-data # frozen_string_literal: true
##
# An Image holds downloaded data for an image/* response.
class Mechanize::Image < Mechanize::Download
end
mechanize-2.10.1/lib/mechanize/chunked_termination_error.rb 0000644 0000041 0000041 00000000316 14645745627 024113 0 ustar www-data www-data # frozen_string_literal: true
##
# Raised when Mechanize detects the chunked transfer-encoding may be
# incorrectly terminated.
class Mechanize::ChunkedTerminationError < Mechanize::ResponseReadError
end
mechanize-2.10.1/lib/mechanize/util.rb 0000644 0000041 0000041 00000011017 14645745627 017625 0 ustar www-data www-data # frozen_string_literal: true
require 'cgi'
require 'nkf'
class Mechanize::Util
# default mime type data for Page::Image#mime_type.
# You can use another Apache-compatible mimetab.
# mimetab = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types')
# Mechanize::Util::DefaultMimeTypes.replace(mimetab)
DefaultMimeTypes = WEBrick::HTTPUtils::DefaultMimeTypes
class << self
# Builds a query string from a given enumerable object
# +parameters+. This method uses Mechanize::Util.each_parameter
# as preprocessor, which see.
def build_query_string(parameters, enc = nil)
each_parameter(parameters).inject(nil) { |s, (k, v)|
# WEBrick::HTTP.escape* has some problems about m17n on ruby-1.9.*.
(s.nil? ? String.new : s << '&') << [CGI.escape(k.to_s), CGI.escape(v.to_s)].join('=')
} || ''
end
# Parses an enumerable object +parameters+ and iterates over the
# key-value pairs it contains.
#
# +parameters+ may be a hash, or any enumerable object which
# iterates over [key, value] pairs, typically an array of arrays.
#
# If a key is paired with an array-like object, the pair is
# expanded into multiple occurrences of the key, one for each
# element of the array. e.g. { a: [1, 2] } => [:a, 1], [:a, 2]
#
# If a key is paired with a hash-like object, the pair is expanded
# into hash-like multiple pairs, one for each pair of the hash.
# e.g. { a: { x: 1, y: 2 } } => ['a[x]', 1], ['a[y]', 2]
#
# An array-like value is allowed to be specified as hash value.
# e.g. { a: { q: [1, 2] } } => ['a[q]', 1], ['a[q]', 2]
#
# For a non-array-like, non-hash-like value, the key-value pair is
# yielded as is.
def each_parameter(parameters, &block)
return to_enum(__method__, parameters) if block.nil?
parameters.each { |key, value|
each_parameter_1(key, value, &block)
}
end
private
def each_parameter_1(key, value, &block)
return if key.nil?
case
when s = String.try_convert(value)
yield [key, s]
when a = Array.try_convert(value)
a.each { |avalue|
yield [key, avalue]
}
when h = Hash.try_convert(value)
h.each { |hkey, hvalue|
each_parameter_1('%s[%s]' % [key, hkey], hvalue, &block)
}
else
yield [key, value]
end
end
end
# Converts string +s+ from +code+ to UTF-8.
def self.from_native_charset(s, code, ignore_encoding_error = false, log = nil)
return s unless s && code
return s unless Mechanize.html_parser == Nokogiri::HTML
begin
s.encode(code)
rescue EncodingError => ex
log.debug("from_native_charset: #{ex.class}: form encoding: #{code.inspect} string: #{s}") if log
if ignore_encoding_error
s
else
raise
end
end
end
def self.html_unescape(s)
return s unless s
s.gsub(/&(\w+|#[0-9]+);/) { |match|
number = case match
when /&(\w+);/
Mechanize.html_parser::NamedCharacters[$1]
when /([0-9]+);/
$1.to_i
end
number ? ([number].pack('U') rescue match) : match
}
end
case NKF::BINARY
when Encoding
def self.guess_encoding(src)
# NKF.guess of JRuby may return nil
NKF.guess(src) || Encoding::US_ASCII
end
else
# Old NKF from 1.8, still bundled with Rubinius
NKF_ENCODING_MAP = {
NKF::UNKNOWN => Encoding::US_ASCII,
NKF::BINARY => Encoding::ASCII_8BIT,
NKF::ASCII => Encoding::US_ASCII,
NKF::JIS => Encoding::ISO_2022_JP,
NKF::EUC => Encoding::EUC_JP,
NKF::SJIS => Encoding::Shift_JIS,
NKF::UTF8 => Encoding::UTF_8,
NKF::UTF16 => Encoding::UTF_16BE,
NKF::UTF32 => Encoding::UTF_32BE,
}
def self.guess_encoding(src)
NKF_ENCODING_MAP[NKF.guess(src)]
end
end
def self.detect_charset(src)
if src
guess_encoding(src).name.upcase
else
Encoding::ISO8859_1.name
end
end
def self.uri_escape str, unsafe = nil
@parser ||= begin
URI::Parser.new
rescue NameError
URI
end
if URI == @parser then
unsafe ||= URI::UNSAFE
else
unsafe ||= @parser.regexp[:UNSAFE]
end
@parser.escape str, unsafe
end
def self.uri_unescape str
@parser ||= begin
URI::Parser.new
rescue NameError
URI
end
@parser.unescape str
end
end
mechanize-2.10.1/lib/mechanize/headers.rb 0000644 0000041 0000041 00000000650 14645745627 020264 0 ustar www-data www-data # frozen_string_literal: true
class Mechanize::Headers < Hash
def [](key)
super(key.downcase)
end
def []=(key, value)
super(key.downcase, value)
end
def key?(key)
super(key.downcase)
end
def canonical_each
block_given? or return enum_for(__method__)
each { |key, value|
key = key.capitalize
key.gsub!(/-([a-z])/) { "-#{$1.upcase}" }
yield [key, value]
}
end
end
mechanize-2.10.1/lib/mechanize/cookie_jar.rb 0000644 0000041 0000041 00000011061 14645745627 020754 0 ustar www-data www-data # frozen_string_literal: true
warn 'mechanize/cookie_jar will be deprecated. Please migrate to the http-cookie APIs.' if $VERBOSE
require 'http/cookie_jar'
require 'http/cookie_jar/yaml_saver'
require 'mechanize/cookie'
class Mechanize
module CookieJarIMethods
include CookieDeprecated
def add(arg1, arg2 = nil)
if arg2
__deprecated__ 'add and origin='
super arg2.dup.tap { |ncookie|
begin
ncookie.origin = arg1
rescue
return nil
end
}
else
super arg1
end
end
# See HTTP::CookieJar#add.
def add!(cookie)
__deprecated__ :add
cookie.domain.nil? and raise NoMethodError, 'raised for compatibility'
@store.add(cookie)
self
end
# See HTTP::CookieJar#save.
def save_as(filename, *options)
__deprecated__ :save
save(filename, *options)
end
# See HTTP::CookieJar#clear.
def clear!
__deprecated__ :clear
clear
end
# See HTTP::CookieJar#store.
def jar
__deprecated__ :store
@store.instance_variable_get(:@jar)
end
# See HTTP::CookieJar#load.
def load_cookiestxt(io)
__deprecated__ :load
load(io, :cookiestxt)
end
# See HTTP::CookieJar#save.
def dump_cookiestxt(io)
__deprecated__ :save
save(io, :cookiestxt)
end
end
class CookieJar < ::HTTP::CookieJar
def save(output, *options)
output.respond_to?(:write) or
return ::File.open(output, 'w') { |io| save(io, *options) }
opthash = {
:format => :yaml,
:session => false,
}
case options.size
when 0
when 1
case options = options.first
when Symbol
opthash[:format] = options
else
opthash.update(options) if options
end
when 2
opthash[:format], options = options
opthash.update(options) if options
else
raise ArgumentError, 'wrong number of arguments (%d for 1-3)' % (1 + options.size)
end
return super(output, opthash) if opthash[:format] != :yaml
session = opthash[:session]
nstore = HashStore.new
each { |cookie|
next if !session && cookie.session?
if cookie.max_age
cookie = cookie.dup
cookie.expires = cookie.expires # convert max_age to expires
end
nstore.add(cookie)
}
yaml = YAML.dump(nstore.instance_variable_get(:@jar))
# a gross hack
yaml.gsub!(%r{^( [^ ].*: !ruby/object:)HTTP::Cookie$}) {
$1 + 'Mechanize::Cookie'
}
yaml.gsub!(%r{^( expires: )(?:|!!null|(.+?)) *$}) {
$1 + ($2 ? Time.parse($2).httpdate : '')
}
output.write yaml
self
end
def load(input, *options)
input.respond_to?(:write) or
return ::File.open(input, 'r') { |io| load(io, *options) }
opthash = {
:format => :yaml,
:session => false,
}
case options.size
when 0
when 1
case options = options.first
when Symbol
opthash[:format] = options
else
if hash = Hash.try_convert(options)
opthash.update(hash)
end
end
when 2
opthash[:format], options = options
if hash = Hash.try_convert(options)
opthash.update(hash)
end
else
raise ArgumentError, 'wrong number of arguments (%d for 1-3)' % (1 + options.size)
end
return super(input, opthash) if opthash[:format] != :yaml
begin
data = load_yaml(input)
rescue ArgumentError
@logger.warn "unloadable YAML cookie data discarded" if @logger
return self
end
case data
when Array
# Forward compatibility
data.each { |cookie|
add(cookie)
}
when Hash
data.each { |domain, paths|
paths.each { |path, names|
names.each { |cookie_name, cookie|
add(cookie)
}
}
}
else
@logger.warn "incompatible YAML cookie data discarded" if @logger
return self
end
end
private
if YAML.name == "Psych" && Gem::Requirement.new(">= 3.1").satisfied_by?(Gem::Version.new(Psych::VERSION))
def load_yaml(yaml)
YAML.safe_load(yaml, aliases: true, permitted_classes: ["Mechanize::Cookie", "Time"])
end
else
def load_yaml(yaml)
YAML.load(yaml) # rubocop:disable Security/YAMLLoad
end
end
end
class ::HTTP::CookieJar
prepend CookieJarIMethods
end
end
mechanize-2.10.1/lib/mechanize/content_type_error.rb 0000644 0000041 0000041 00000000625 14645745627 022577 0 ustar www-data www-data # frozen_string_literal: true
##
# This error is raised when a pluggable parser tries to parse a content type
# that it does not know how to handle. For example if Mechanize::Page were to
# try to parse a PDF, a ContentTypeError would be thrown.
class Mechanize::ContentTypeError < Mechanize::Error
attr_reader :content_type
def initialize(content_type)
@content_type = content_type
end
end
mechanize-2.10.1/lib/mechanize/form/ 0000755 0000041 0000041 00000000000 14645745627 017266 5 ustar www-data www-data mechanize-2.10.1/lib/mechanize/form/select_list.rb 0000644 0000041 0000041 00000002077 14645745627 022133 0 ustar www-data www-data # frozen_string_literal: true
# This class represents a select list or drop down box in a Form. Set the
# value for the list by calling SelectList#value=. SelectList contains a list
# of Option that were found. After finding the correct option, set the select
# lists value to the option value:
#
# selectlist.value = selectlist.options.first.value
#
# Options can also be selected by "clicking" or selecting them. See Option
class Mechanize::Form::SelectList < Mechanize::Form::MultiSelectList
def initialize node
super
if selected_options.length > 1
selected_options.reverse[1..selected_options.length].each do |o|
o.unselect
end
end
end
def value
value = super
if value.length > 0
value.last
elsif @options.length > 0
@options.first.value
else
nil
end
end
def value=(new_value)
if new_value != new_value.to_s and new_value.respond_to? :first
super([new_value.first])
else
super([new_value.to_s])
end
end
def query_value
value ? [[name, value]] : nil
end
end
mechanize-2.10.1/lib/mechanize/form/text.rb 0000644 0000041 0000041 00000000130 14645745627 020571 0 ustar www-data www-data # frozen_string_literal: true
class Mechanize::Form::Text < Mechanize::Form::Field
end
mechanize-2.10.1/lib/mechanize/form/submit.rb 0000644 0000041 0000041 00000000133 14645745627 021113 0 ustar www-data www-data # frozen_string_literal: true
class Mechanize::Form::Submit < Mechanize::Form::Button
end
mechanize-2.10.1/lib/mechanize/form/hidden.rb 0000644 0000041 0000041 00000000132 14645745627 021042 0 ustar www-data www-data # frozen_string_literal: true
class Mechanize::Form::Hidden < Mechanize::Form::Field
end
mechanize-2.10.1/lib/mechanize/form/button.rb 0000644 0000041 0000041 00000000172 14645745627 021126 0 ustar www-data www-data # frozen_string_literal: true
##
# A Submit button in a Form
class Mechanize::Form::Button < Mechanize::Form::Field
end
mechanize-2.10.1/lib/mechanize/form/check_box.rb 0000644 0000041 0000041 00000000725 14645745627 021544 0 ustar www-data www-data # frozen_string_literal: true
##
# This class represents a check box found in a Form. To activate the CheckBox
# in the Form, set the checked method to true.
class Mechanize::Form::CheckBox < Mechanize::Form::RadioButton
def query_value
[[@name, @value || "on"]]
end
def inspect # :nodoc:
"[%s:0x%x type: %s name: %s value: %s]" % [
self.class.name.sub(/Mechanize::Form::/, '').downcase,
object_id, type, name, checked
]
end
end
mechanize-2.10.1/lib/mechanize/form/option.rb 0000644 0000041 0000041 00000002237 14645745627 021127 0 ustar www-data www-data # frozen_string_literal: true
##
# This class contains an option found within SelectList. A SelectList can
# have many Option classes associated with it. An option can be selected by
# calling Option#tick, or Option#click.
#
# To select the first option in a list:
#
# select_list.first.tick
class Mechanize::Form::Option
attr_reader :value, :selected, :text, :select_list, :node
alias :to_s :value
alias :selected? :selected
def initialize(node, select_list)
@node = node
@text = node.inner_text
@value = Mechanize::Util.html_unescape(node['value'] || node.inner_text)
@selected = node.has_attribute? 'selected'
@select_list = select_list # The select list this option belongs to
end
# Select this option
def select
unselect_peers
@selected = true
end
# Unselect this option
def unselect
@selected = false
end
alias :tick :select
alias :untick :unselect
# Toggle the selection value of this option
def click
unselect_peers
@selected = !@selected
end
private
def unselect_peers
return unless Mechanize::Form::SelectList === @select_list
@select_list.select_none
end
end
mechanize-2.10.1/lib/mechanize/form/radio_button.rb 0000644 0000041 0000041 00000002255 14645745627 022310 0 ustar www-data www-data # frozen_string_literal: true
##
# This class represents a radio button found in a Form. To activate the
# RadioButton in the Form, set the checked method to true.
class Mechanize::Form::RadioButton < Mechanize::Form::Field
attr_accessor :checked
attr_reader :form
def initialize node, form
@checked = !!node['checked']
@form = form
super(node)
end
def == other # :nodoc:
self.class === other and
other.form == @form and
other.name == @name and
other.value == @value
end
alias eql? == # :nodoc:
def check
uncheck_peers
@checked = true
end
alias checked? checked
def uncheck
@checked = false
end
def click
checked ? uncheck : check
end
def hash # :nodoc:
@form.hash ^ @name.hash ^ @value.hash
end
def label
(id = self['id']) && @form.page.labels_hash[id] || nil
end
def text
label.text rescue nil
end
def [](key)
@node[key]
end
def pretty_print_instance_variables # :nodoc:
[:@checked, :@name, :@value]
end
private
def uncheck_peers
@form.radiobuttons_with(:name => name).each do |b|
next if b.value == value
b.uncheck
end
end
end
mechanize-2.10.1/lib/mechanize/form/image_button.rb 0000644 0000041 0000041 00000000652 14645745627 022273 0 ustar www-data www-data # frozen_string_literal: true
##
# This class represents an image button in a form. Use the x and y methods to
# set the x and y positions for where the mouse "clicked".
class Mechanize::Form::ImageButton < Mechanize::Form::Button
attr_accessor :x, :y
def initialize *args
@x = nil
@y = nil
super
end
def query_value
[["#{@name}.x", (@x || 0).to_s],
["#{@name}.y", (@y || 0).to_s]]
end
end
mechanize-2.10.1/lib/mechanize/form/file_upload.rb 0000644 0000041 0000041 00000001226 14645745627 022077 0 ustar www-data www-data # frozen_string_literal: true
# This class represents a file upload field found in a form. To use this
# class, set FileUpload#file_data= to the data of the file you want to upload
# and FileUpload#mime_type= to the appropriate mime type of the file.
#
# See the example in EXAMPLES
class Mechanize::Form::FileUpload < Mechanize::Form::Field
attr_accessor :file_name # File name
attr_accessor :mime_type # Mime Type (Optional)
alias :file_data :value
alias :file_data= :value=
def initialize node, file_name
@file_name = Mechanize::Util.html_unescape(file_name)
@file_data = nil
@node = node
super(node, @file_data)
end
end
mechanize-2.10.1/lib/mechanize/form/keygen.rb 0000644 0000041 0000041 00000001766 14645745627 021107 0 ustar www-data www-data # frozen_string_literal: true
##
# This class represents a keygen (public / private key generator) found in a
# Form. The field will automatically generate a key pair and compute its own
# value to match the challenge. Call key to access the public/private key
# pair.
class Mechanize::Form::Keygen < Mechanize::Form::Field
# The challenge for this .
attr_reader :challenge
# The key associated with this tag.
attr_reader :key
def initialize(node, value = nil)
super
@challenge = node['challenge']
@spki = OpenSSL::Netscape::SPKI.new
@spki.challenge = @challenge
@key = nil
generate_key if value.nil? || value.empty?
end
# Generates a key pair and sets the field's value.
def generate_key(key_size = 2048)
# Spec at http://dev.w3.org/html5/spec/Overview.html#the-keygen-element
@key = OpenSSL::PKey::RSA.new key_size
@spki.public_key = @key.public_key
@spki.sign @key, OpenSSL::Digest::MD5.new
self.value = @spki.to_pem
end
end
mechanize-2.10.1/lib/mechanize/form/reset.rb 0000644 0000041 0000041 00000000132 14645745627 020731 0 ustar www-data www-data # frozen_string_literal: true
class Mechanize::Form::Reset < Mechanize::Form::Button
end
mechanize-2.10.1/lib/mechanize/form/multi_select_list.rb 0000644 0000041 0000041 00000003574 14645745627 023350 0 ustar www-data www-data # frozen_string_literal: true
##
# This class represents a select list where multiple values can be selected.
# MultiSelectList#value= accepts an array, and those values are used as
# values for the select list. For example, to select multiple values,
# simply do this:
#
# list.value = ['one', 'two']
#
# Single values are still supported, so these two are the same:
#
# list.value = ['one']
# list.value = 'one'
class Mechanize::Form::MultiSelectList < Mechanize::Form::Field
extend Mechanize::ElementMatcher
attr_accessor :options
def initialize node
value = []
@options = node.search('option').map { |n|
Mechanize::Form::Option.new(n, self)
}
super node, value
end
##
# :method: option_with
#
# Find one option on this select list with +criteria+
#
# Example:
#
# select_list.option_with(:value => '1').value = 'foo'
##
# :method: option_with!(criteria)
#
# Same as +option_with+ but raises an ElementNotFoundError if no button
# matches +criteria+
##
# :method: options_with
#
# Find all options on this select list with +criteria+
#
# Example:
#
# select_list.options_with(:value => /1|2/).each do |field|
# field.value = '20'
# end
elements_with :option
def query_value
value ? value.map { |v| [name, v] } : ''
end
# Select no options
def select_none
@value = []
options.each(&:untick)
end
# Select all options
def select_all
@value = []
options.each(&:tick)
end
# Get a list of all selected options
def selected_options
@options.find_all(&:selected?)
end
def value=(values)
select_none
[values].flatten.each do |value|
option = options.find { |o| o.value == value }
if option.nil?
@value.push(value)
else
option.select
end
end
end
def value
@value + selected_options.map(&:value)
end
end
mechanize-2.10.1/lib/mechanize/form/field.rb 0000644 0000041 0000041 00000004777 14645745627 020715 0 ustar www-data www-data # frozen_string_literal: true
##
# This class represents a field in a form. It handles the following input
# tags found in a form:
#
# * text
# * password
# * hidden
# * int
# * textarea
# * keygen
#
# To set the value of a field, just use the value method:
#
# field.value = "foo"
class Mechanize::Form::Field
extend Forwardable
attr_accessor :name, :value, :node, :type
# This fields value before it's sent through Util.html_unescape.
attr_reader :raw_value
# index is used to maintain order for fields with Hash nodes
attr_accessor :index
def initialize node, value = node['value']
@node = node
@name = Mechanize::Util.html_unescape(node['name'])
@raw_value = value
@value = if value.is_a? String
Mechanize::Util.html_unescape(value)
else
value
end
@type = node['type']
end
def query_value
[[@name, @value || '']]
end
def <=> other
return 0 if self == other
# If both are hashes, sort by index
if Hash === node && Hash === other.node && index
return index <=> other.index
end
# Otherwise put Hash based fields at the end
return 1 if Hash === node
return -1 if Hash === other.node
# Finally let nokogiri determine sort order
node <=> other.node
end
# This method is a shortcut to get field's DOM id.
# Common usage: form.field_with(:dom_id => "foo")
def dom_id
node['id']
end
# This method is a shortcut to get field's DOM class.
# Common usage: form.field_with(:dom_class => "foo")
def dom_class
node['class']
end
##
# :method: search
#
# Shorthand for +node.search+.
#
# See Nokogiri::XML::Node#search for details.
##
# :method: css
#
# Shorthand for +node.css+.
#
# See also Nokogiri::XML::Node#css for details.
##
# :method: xpath
#
# Shorthand for +node.xpath+.
#
# See also Nokogiri::XML::Node#xpath for details.
##
# :method: at
#
# Shorthand for +node.at+.
#
# See also Nokogiri::XML::Node#at for details.
##
# :method: at_css
#
# Shorthand for +node.at_css+.
#
# See also Nokogiri::XML::Node#at_css for details.
##
# :method: at_xpath
#
# Shorthand for +node.at_xpath+.
#
# See also Nokogiri::XML::Node#at_xpath for details.
def_delegators :node, :search, :css, :xpath, :at, :at_css, :at_xpath
def inspect # :nodoc:
"[%s:0x%x type: %s name: %s value: %s]" % [
self.class.name.sub(/Mechanize::Form::/, '').downcase,
object_id, type, name, value
]
end
end
mechanize-2.10.1/lib/mechanize/form/textarea.rb 0000644 0000041 0000041 00000000134 14645745627 021426 0 ustar www-data www-data # frozen_string_literal: true
class Mechanize::Form::Textarea < Mechanize::Form::Field
end
mechanize-2.10.1/lib/mechanize/element_matcher.rb 0000644 0000041 0000041 00000003423 14645745627 022006 0 ustar www-data www-data # frozen_string_literal: true
module Mechanize::ElementMatcher
def elements_with singular, plural = "#{singular}s"
class_eval <<-CODE
def #{plural}_with criteria = {}
selector = method = nil
if String === criteria then
criteria = {:name => criteria}
else
criteria = criteria.each_with_object({}) { |(k, v), h|
case k = k.to_sym
when :id
h[:dom_id] = v
when :class
h[:dom_class] = v
when :search, :xpath, :css
if v
if method
warn "multiple search selectors are given; previous selector (\#{method}: \#{selector.inspect}) is ignored."
end
selector = v
method = k
end
else
h[k] = v
end
}
end
f = select_#{plural}(selector, method).find_all do |thing|
criteria.all? do |k,v|
v === thing.__send__(k)
end
end
yield f if block_given?
f
end
def #{singular}_with criteria = {}
f = #{plural}_with(criteria).first
yield f if block_given?
f
end
def #{singular}_with! criteria = {}
f = #{singular}_with(criteria)
raise Mechanize::ElementNotFoundError.new(self, :#{singular}, criteria) if f.nil?
yield f if block_given?
f
end
def select_#{plural} selector, method = :search
if selector.nil? then
#{plural}
else
nodes = __send__(method, selector)
#{plural}.find_all do |element|
nodes.include?(element.node)
end
end
end
alias :#{singular} :#{singular}_with
CODE
end
end
mechanize-2.10.1/lib/mechanize/page/ 0000755 0000041 0000041 00000000000 14645745627 017237 5 ustar www-data www-data mechanize-2.10.1/lib/mechanize/page/link.rb 0000644 0000041 0000041 00000005154 14645745627 020526 0 ustar www-data www-data # frozen_string_literal: true
##
# This class encapsulates links. It contains the text and the URI for
# 'a' tags parsed out of an HTML page. If the link contains an image,
# the alt text will be used for that image.
#
# For example, the text for the following links with both be 'Hello World':
#
# Hello World
#
require 'addressable/uri'
class Mechanize::Page::Link
attr_reader :node
attr_reader :href
attr_reader :attributes
attr_reader :page
alias :referer :page
def initialize(node, mech, page)
@node = node
@attributes = node
@href = node['href']
@mech = mech
@page = page
@text = nil
@uri = nil
end
# Click on this link
def click
@mech.click self
end
# This method is a shorthand to get link's DOM id.
# Common usage:
# page.link_with(:dom_id => "links_exact_id")
def dom_id
node['id']
end
# This method is a shorthand to get a link's DOM class
# Common usage:
# page.link_with(:dom_class => "links_exact_class")
def dom_class
node['class']
end
def pretty_print(q) # :nodoc:
q.object_group(self) {
q.breakable; q.pp text
q.breakable; q.pp href
}
end
alias inspect pretty_inspect # :nodoc:
# A list of words in the rel attribute, all lower-cased.
def rel
@rel ||= (val = attributes['rel']) ? val.downcase.split(' ') : []
end
# Test if the rel attribute includes +kind+.
def rel? kind
rel.include? kind
end
# Test if this link should not be traced.
def noreferrer?
rel?('noreferrer')
end
# The text content of this link
def text
return @text if @text
@text = @node.inner_text
# If there is no text, try to find an image and use it's alt text
if (@text.nil? or @text.empty?) and imgs = @node.search('img') then
@text = imgs.map do |e|
e['alt']
end.join
end
@text
end
alias :to_s :text
# A URI for the #href for this link. The link is first parsed as a raw
# link. If that fails parsing an escaped link is attepmted.
def uri
@uri ||= if @href then
begin
URI.parse @href
rescue URI::InvalidURIError
begin
URI.parse(Addressable::URI.escape(@href))
rescue Addressable::URI::InvalidURIError
raise URI::InvalidURIError
end
end
end
end
# A fully resolved URI for the #href for this link.
def resolved_uri
@mech.resolve uri
end
end
mechanize-2.10.1/lib/mechanize/page/meta_refresh.rb 0000644 0000041 0000041 00000004054 14645745627 022233 0 ustar www-data www-data # frozen_string_literal: true
##
# This class encapsulates a meta element with a refresh http-equiv. Mechanize
# treats meta refresh elements just like 'a' tags. MetaRefresh objects will
# contain links, but most likely will have no text.
class Mechanize::Page::MetaRefresh < Mechanize::Page::Link
##
# Time to wait before next refresh
attr_reader :delay
##
# This MetaRefresh links did not contain a url= in the content attribute and
# links to itself.
attr_reader :link_self
##
# Matches the content attribute of a meta refresh element. After the match:
#
# $1:: delay
# $3:: url
CONTENT_REGEXP = /^\s*(\d+\.?\d*)\s*(?:;(?:\s*url\s*=\s*(['"]?)(\S*)\2)?\s*)?$/i
##
# Regexp of unsafe URI characters that excludes % for Issue #177
UNSAFE = /[^\-_.!~*'()a-zA-Z\d;\/?:@&%=+$,\[\]]/
##
# Parses the delay and url from the content attribute of a meta
# refresh element.
#
# Returns an array of [delay, url, link_self], where the first two
# are strings containing the respective parts of the refresh value,
# and link_self is a boolean value that indicates whether the url
# part is missing or empty. If base_uri, the URI of the current
# page is given, the value of url becomes an absolute URI.
def self.parse content, base_uri = nil
m = CONTENT_REGEXP.match(content) or return
delay, url = m[1], m[3]
url &&= url.empty? ? nil : Mechanize::Util.uri_escape(url, UNSAFE)
link_self = url.nil?
if base_uri
url = url ? base_uri + url : base_uri
end
return delay, url, link_self
end
def self.from_node node, page, uri = nil
http_equiv = node['http-equiv'] and
/\ARefresh\z/i =~ http_equiv or return
delay, uri, link_self = parse node['content'], uri
return unless delay
new node, page, delay, uri, link_self
end
def initialize node, page, delay, href, link_self = false
super node, page.mech, page
@delay = delay.include?(?.) ? delay.to_f : delay.to_i
@href = href
@link_self = link_self
end
def noreferrer?
true
end
end
mechanize-2.10.1/lib/mechanize/page/image.rb 0000644 0000041 0000041 00000006443 14645745627 020655 0 ustar www-data www-data # frozen_string_literal: true
##
# An image element on an HTML page
class Mechanize::Page::Image
attr_reader :node
attr_accessor :page
attr_accessor :mech
##
# Creates a new Mechanize::Page::Image from an image +node+ and source
# +page+.
def initialize node, page
@node = node
@page = page
@mech = page.mech
end
##
# The alt attribute of the image
def alt
node['alt']
end
##
# The caption of the image. In order of preference, the #title, #alt, or
# empty string "".
def caption
title || alt || ''
end
alias :text :caption
##
# The class attribute of the image
def dom_class
node['class']
end
##
# The id attribute of the image
def dom_id
node['id']
end
##
# The suffix of the #url. The dot is a part of suffix, not a delimiter.
#
# p image.url # => "http://example/test.jpg"
# p image.extname # => ".jpg"
#
# Returns an empty string if #url has no suffix:
#
# p image.url # => "http://example/sampleimage"
# p image.extname # => ""
def extname
return nil unless src
File.extname url.path
end
##
# Downloads the image.
#
# agent.page.image_with(:src => /logo/).fetch.save
#
# The referer is:
#
# #page("parent") ::
# all images on http html, relative #src images on https html
# (no referer) ::
# absolute #src images on https html
# user specified ::
# img.fetch(nil, my_referer_uri_or_page)
def fetch parameters = [], referer = nil, headers = {}
mech.get src, parameters, referer || image_referer, headers
end
##
# The height attribute of the image
def height
node['height']
end
def image_referer # :nodoc:
http_page = page.uri && page.uri.scheme == 'http'
https_page = page.uri && page.uri.scheme == 'https'
case
when http_page then page
when https_page && relative? then page
else
Mechanize::File.new(nil, { 'content-type' => 'text/plain' }, '', 200)
end
end
##
# MIME type guessed from the image url suffix
#
# p image.extname # => ".jpg"
# p image.mime_type # => "image/jpeg"
# page.images_with(:mime_type => /gif|jpeg|png/).each do ...
#
# Returns nil if url has no (well-known) suffix:
#
# p image.url # => "http://example/sampleimage"
# p image.mime_type # => nil
def mime_type
suffix_without_dot = extname ? extname.sub(/\A\./){''}.downcase : nil
Mechanize::Util::DefaultMimeTypes[suffix_without_dot]
end
def pretty_print(q) # :nodoc:
q.object_group(self) {
q.breakable; q.pp url
q.breakable; q.pp caption
}
end
alias inspect pretty_inspect # :nodoc:
def relative? # :nodoc:
%r{^https?://} !~ src
end
##
# The src attribute of the image
def src
node['src']
end
##
# The title attribute of the image
def title
node['title']
end
##
# The URL string of this image
def to_s
url.to_s
end
##
# URI for this image
def url
if relative? then
if page.bases[0] then
page.bases[0].href + src.to_s
else
page.uri + Mechanize::Util.uri_escape(src.to_s)
end
else
URI Mechanize::Util.uri_escape(src)
end
end
alias uri url
##
# The width attribute of the image
def width
node['width']
end
end
mechanize-2.10.1/lib/mechanize/page/base.rb 0000644 0000041 0000041 00000000364 14645745627 020501 0 ustar www-data www-data # frozen_string_literal: true
##
# A base element on an HTML page. Mechanize treats base tags just like 'a'
# tags. Base objects will contain links, but most likely will have no text.
class Mechanize::Page::Base < Mechanize::Page::Link
end
mechanize-2.10.1/lib/mechanize/page/label.rb 0000644 0000041 0000041 00000000536 14645745627 020647 0 ustar www-data www-data # frozen_string_literal: true
##
# A form label on an HTML page
class Mechanize::Page::Label
attr_reader :node
attr_reader :text
attr_reader :page
alias :to_s :text
def initialize(node, page)
@node = node
@text = node.inner_text
@page = page
end
def for
(id = @node['for']) && page.search("##{id}") || nil
end
end
mechanize-2.10.1/lib/mechanize/page/frame.rb 0000644 0000041 0000041 00000001314 14645745627 020655 0 ustar www-data www-data # frozen_string_literal: true
# A Frame object wraps a frame HTML element. Frame objects can be treated
# just like Link objects. They contain #src, the #link they refer to and a
# #name, the name of the frame they refer to. #src and #name are aliased to
# #href and #text respectively so that a Frame object can be treated just like
# a Link.
class Mechanize::Page::Frame < Mechanize::Page::Link
alias :src :href
attr_reader :text
alias :name :text
attr_reader :node
def initialize(node, mech, referer)
super(node, mech, referer)
@node = node
@text = node['name']
@href = node['src']
@content = nil
end
def content
@content ||= @mech.get @href, [], page
end
end
mechanize-2.10.1/lib/mechanize/file_request.rb 0000644 0000041 0000041 00000000564 14645745627 021344 0 ustar www-data www-data # frozen_string_literal: true
##
# A wrapper for a file URI that makes a request that works like a
# Net::HTTPRequest
class Mechanize::FileRequest
attr_accessor :uri
def initialize uri
@uri = uri
end
def add_field *a
end
alias []= add_field
def path
@uri.path
end
def each_header
end
def response_body_permitted?
true
end
end
mechanize-2.10.1/lib/mechanize/test_case.rb 0000644 0000041 0000041 00000017224 14645745627 020630 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize'
require 'logger'
require 'tempfile'
require 'tmpdir'
require 'webrick'
require 'zlib'
require 'rubygems'
begin
gem 'minitest'
rescue Gem::LoadError
end
require 'minitest/autorun'
begin
require 'minitest/pride'
rescue LoadError
end
##
# A generic test case for testing mechanize. Using a subclass of
# Mechanize::TestCase for your tests will create an isolated mechanize
# instance that won't pollute your filesystem or other tests.
#
# Once Mechanize::TestCase is loaded no HTTP requests will be made outside
# mechanize itself. All requests are handled via WEBrick servlets.
#
# Mechanize uses WEBrick servlets to test some functionality. You can run
# other HTTP clients against the servlets using:
#
# ruby -rmechanize/test_case/server -e0
#
# Which will launch a test server at http://localhost:8000
class Mechanize::TestCase < Minitest::Test
TEST_DIR = File.expand_path '../../../test', __FILE__
REQUESTS = []
##
# Creates a clean mechanize instance +@mech+ for use in tests.
def setup
super
REQUESTS.clear
@mech = Mechanize.new
@ssl_private_key = nil
@ssl_certificate = nil
end
##
# Creates a fake page with URI http://fake.example and an empty, submittable
# form.
def fake_page agent = @mech
uri = URI 'http://fake.example/'
html = String.new(<<~END)
END
Mechanize::Page.new uri, nil, html, 200, agent
end
##
# Is the Encoding constant defined?
def have_encoding?
Object.const_defined? :Encoding
end
##
# Creates a Mechanize::Page with the given +body+
def html_page body
uri = URI 'http://example/'
Mechanize::Page.new uri, nil, body, 200, @mech
end
##
# Creates a Mechanize::CookieJar by parsing the given +str+
def cookie_jar str, uri = URI('http://example')
Mechanize::CookieJar.new.tap do |jar|
jar.parse str, uri
end
end
##
# Runs the block inside a temporary directory
def in_tmpdir
Dir.mktmpdir do |dir|
Dir.chdir dir do
yield
end
end
end
##
# Creates a Nokogiri Node +element+ with the given +attributes+
def node element, attributes = {}
Nokogiri::XML::Node.new(element, Nokogiri::HTML::Document.new).tap do |node|
attributes.each do |name, value|
node[name] = value
end
end
end
##
# Creates a Mechanize::Page for the given +uri+ with the given
# +content_type+, response +body+ and HTTP status +code+
def page uri, content_type = 'text/html', body = String.new, code = 200
uri = URI uri unless URI::Generic === uri
Mechanize::Page.new(uri, { 'content-type' => content_type }, body, code,
@mech)
end
##
# Requests made during this tests
def requests
REQUESTS
end
##
# An SSL private key. This key is the same across all test runs
def ssl_private_key
@ssl_private_key ||= OpenSSL::PKey::RSA.new <<-KEY
-----BEGIN RSA PRIVATE KEY-----
MIG7AgEAAkEA8pmEfmP0Ibir91x6pbts4JmmsVZd3xvD5p347EFvBCbhBW1nv1Gs
bCBEFlSiT1q2qvxGb5IlbrfdhdgyqdTXUQIBAQIBAQIhAPumXslvf6YasXa1hni3
p80joKOug2UUgqOLD2GUSO//AiEA9ssY6AFxjHWuwo/+/rkLmkfO2s1Lz3OeUEWq
6DiHOK8CAQECAQECIQDt8bc4vS6wh9VXApNSKIpVygtxSFe/IwLeX26n77j6Qg==
-----END RSA PRIVATE KEY-----
KEY
end
##
# An X509 certificate. This certificate is the same across all test runs
def ssl_certificate
@ssl_certificate ||= OpenSSL::X509::Certificate.new <<-CERT
-----BEGIN CERTIFICATE-----
MIIBQjCB7aADAgECAgEAMA0GCSqGSIb3DQEBBQUAMCoxDzANBgNVBAMMBm5vYm9k
eTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUwIBcNMTExMTAzMjEwODU5WhgPOTk5
OTEyMzExMjU5NTlaMCoxDzANBgNVBAMMBm5vYm9keTEXMBUGCgmSJomT8ixkARkW
B2V4YW1wbGUwWjANBgkqhkiG9w0BAQEFAANJADBGAkEA8pmEfmP0Ibir91x6pbts
4JmmsVZd3xvD5p347EFvBCbhBW1nv1GsbCBEFlSiT1q2qvxGb5IlbrfdhdgyqdTX
UQIBATANBgkqhkiG9w0BAQUFAANBAAAB////////////////////////////////
//8AMCEwCQYFKw4DAhoFAAQUePiv+QrJxyjtEJNnH5pB9OTWIqA=
-----END CERTIFICATE-----
CERT
end
##
# Creates a Tempfile with +content+ that is immediately unlinked
def tempfile content
Tempfile.new(@NAME).tap do |body_io|
body_io.unlink
body_io.write content
body_io.flush
body_io.rewind
end
end
##
# Returns true if the current platform is a Windows platform
def windows?
::RUBY_PLATFORM =~ /mingw|mswin/
end
##
# Return the contents of the file without Windows carriage returns
def file_contents_without_cr(path)
File.read(path).gsub(/\r\n/, "\n")
end
end
require 'mechanize/test_case/servlets'
module Net # :nodoc:
end
class Net::HTTP # :nodoc:
alias :old_do_start :do_start
def do_start
@started = true
end
PAGE_CACHE = {}
alias :old_request :request
def request(req, *data, &block)
url = URI.parse(req.path)
path = WEBrick::HTTPUtils.unescape(url.path)
path = '/index.html' if path == '/'
res = ::Response.new
res.query_params = url.query
req.query = if 'POST' != req.method && url.query then
WEBrick::HTTPUtils.parse_query url.query
elsif req['content-type'] =~ /www-form-urlencoded/ then
WEBrick::HTTPUtils.parse_query req.body
elsif req['content-type'] =~ /boundary=(.+)/ then
boundary = WEBrick::HTTPUtils.dequote $1
WEBrick::HTTPUtils.parse_form_data req.body, boundary
else
{}
end
req.cookies = WEBrick::Cookie.parse(req['Cookie'])
Mechanize::TestCase::REQUESTS << req
if servlet_klass = MECHANIZE_TEST_CASE_SERVLETS[path]
servlet = servlet_klass.new({})
servlet.send "do_#{req.method}", req, res
else
filename = "htdocs#{path.gsub(/[^\/\\.\w\s]/, '_')}"
unless PAGE_CACHE[filename]
::File.open("#{Mechanize::TestCase::TEST_DIR}/#{filename}", 'rb') do |io|
PAGE_CACHE[filename] = io.read
end
end
res.body = PAGE_CACHE[filename]
case filename
when /\.txt$/
res['Content-Type'] = 'text/plain'
when /\.jpg$/
res['Content-Type'] = 'image/jpeg'
end
end
res['Content-Type'] ||= 'text/html'
res.code ||= "200"
response_klass = Net::HTTPResponse::CODE_TO_OBJ[res.code.to_s]
response = response_klass.new res.http_version, res.code, res.message
res.header.each do |k,v|
v = v.first if v.length == 1
response[k] = v
end
res.cookies.each do |cookie|
response.add_field 'Set-Cookie', cookie.to_s
end
response['Content-Type'] ||= 'text/html'
response['Content-Length'] = res['Content-Length'] || res.body.length.to_s
io = StringIO.new(res.body)
response.instance_variable_set :@socket, io
def io.read clen, dest = nil, _ = nil
if dest then
dest << super(clen)
else
super clen
end
end
body_exist = req.response_body_permitted? &&
response_klass.body_permitted?
response.instance_variable_set :@body_exist, body_exist
yield response if block_given?
response
end
end
class Net::HTTPRequest # :nodoc:
attr_accessor :query, :body, :cookies, :user
def host
'example'
end
def port
80
end
end
class Response # :nodoc:
include Net::HTTPHeader
attr_reader :code
attr_accessor :body, :query, :cookies
attr_accessor :query_params, :http_version
attr_accessor :header
def code=(c)
@code = c.to_s
end
alias :status :code
alias :status= :code=
def initialize
@header = {}
@body = String.new
@code = nil
@query = nil
@cookies = []
@http_version = '1.1'
end
def read_body
yield body
end
def message
''
end
end
mechanize-2.10.1/lib/mechanize/unauthorized_error.rb 0000644 0000041 0000041 00000000632 14645745627 022603 0 ustar www-data www-data # frozen_string_literal: true
class Mechanize::UnauthorizedError < Mechanize::ResponseCodeError
attr_reader :challenges
def initialize page, challenges, message
super page, message
@challenges = challenges
end
def to_s
out = super
if @challenges then
realms = @challenges.map(&:realm_name).join ', '
out << " -- available realms: #{realms}"
end
out
end
end
mechanize-2.10.1/lib/mechanize/response_code_error.rb 0000644 0000041 0000041 00000001450 14645745627 022711 0 ustar www-data www-data # frozen_string_literal: true
# This error is raised when Mechanize encounters a response code it does not
# know how to handle. Currently, this exception will be thrown if Mechanize
# encounters response codes other than 200, 301, or 302. Any other response
# code is up to the user to handle.
class Mechanize::ResponseCodeError < Mechanize::Error
attr_reader :response_code
attr_reader :page
def initialize(page, message = nil)
super message
@page = page
@response_code = page.code.to_s
end
def to_s
response_class = Net::HTTPResponse::CODE_TO_OBJ[@response_code]
out = String.new("#{@response_code} => #{response_class} ")
out << "for #{@page.uri} " if @page.respond_to? :uri # may be HTTPResponse
out << "-- #{super}"
end
alias inspect to_s
end
mechanize-2.10.1/lib/mechanize/robots_disallowed_error.rb 0000644 0000041 0000041 00000001354 14645745627 023603 0 ustar www-data www-data # frozen_string_literal: true
# Exception that is raised when an access to a resource is disallowed by
# robots.txt or by HTML document itself.
class Mechanize::RobotsDisallowedError < Mechanize::Error
def initialize(url)
if url.is_a?(URI)
@url = url.to_s
@uri = url
else
@url = url.to_s
end
end
# Returns the URL (string) of the resource that caused this error.
attr_reader :url
# Returns the URL (URI object) of the resource that caused this
# error. URI::InvalidURIError may be raised if the URL happens to
# be invalid or not understood by the URI library.
def uri
@uri ||= URI.parse(url)
end
def to_s
"Robots access is disallowed for URL: #{url}"
end
alias :inspect :to_s
end
mechanize-2.10.1/lib/mechanize/download.rb 0000644 0000041 0000041 00000003557 14645745627 020471 0 ustar www-data www-data # frozen_string_literal: true
##
# Download is a pluggable parser for downloading files without loading them
# into memory first. You may subclass this class to handle content types you
# do not wish to load into memory first.
#
# See Mechanize::PluggableParser for instructions on using this class.
class Mechanize::Download
include Mechanize::Parser
##
# The filename for this file based on the content-disposition of the
# response or the basename of the URL
attr_accessor :filename
##
# Accessor for the IO-like that contains the body
attr_reader :body_io
alias content body_io
##
# Creates a new download retrieved from the given +uri+ and +response+
# object. The +body_io+ is an IO-like containing the HTTP response body and
# +code+ is the HTTP status.
def initialize uri = nil, response = nil, body_io = nil, code = nil
@uri = uri
@body_io = body_io
@code = code
@full_path = false unless defined? @full_path
fill_header response
extract_filename
yield self if block_given?
end
##
# The body of this response as a String.
#
# Take care, this may use lots of memory if the response body is large.
def body
@body_io.read.tap { @body_io.rewind }
end
##
# Saves a copy of the body_io to +filename+
# returns the filename
def save filename = nil
filename = find_free_name filename
save! filename
end
alias save_as save
##
# Use this method to save the content of body_io to +filename+.
# This method will overwrite any existing filename that exists with the
# same name.
# returns the filename
def save! filename = nil
filename ||= @filename
dirname = File.dirname filename
FileUtils.mkdir_p dirname
::File.open(filename, 'wb')do |io|
until @body_io.eof? do
io.write @body_io.read 16384
end
end
filename
end
end
mechanize-2.10.1/lib/mechanize/page.rb 0000644 0000041 0000041 00000035027 14645745627 017573 0 ustar www-data www-data # frozen_string_literal: true
##
# This class encapsulates an HTML page. If Mechanize finds a content
# type of 'text/html', this class will be instantiated and returned.
#
# Example:
#
# require 'mechanize'
#
# agent = Mechanize.new
# agent.get('http://google.com/').class # => Mechanize::Page
class Mechanize::Page < Mechanize::File
extend Forwardable
extend Mechanize::ElementMatcher
DEFAULT_RESPONSE = {
'content-type' => 'text/html',
}.freeze
attr_accessor :mech
##
# Possible encodings for this page based on HTTP headers and meta elements
attr_reader :encodings
def initialize(uri=nil, response=nil, body=nil, code=nil, mech=nil)
response ||= DEFAULT_RESPONSE
@meta_content_type = nil
@encoding = nil
@encodings = [nil]
raise 'no' if mech and not Mechanize === mech
@mech = mech
reset
@encodings << Mechanize::Util.detect_charset(body) if body
@encodings.concat self.class.response_header_charset(response)
if body
@encodings.concat self.class.meta_charset body
meta_content_type = self.class.meta_content_type body
@meta_content_type = meta_content_type if meta_content_type
end
@encodings << mech.default_encoding if mech and mech.default_encoding
super uri, response, body, code
end
def title
@title ||=
if doc = parser
title = doc.xpath('string(((/html/head | /html | /head | /)/title)[1])').to_s
title.empty? ? nil : title
end
end
def response_header_charset
self.class.response_header_charset(response)
end
def meta_charset
self.class.meta_charset(body)
end
def detected_encoding
Mechanize::Util.detect_charset(body)
end
def encoding=(encoding)
reset
@encoding = encoding
if @parser
parser_encoding = @parser.encoding
if parser_encoding && encoding && parser_encoding.casecmp(encoding) != 0
# lazy reinitialize the parser with the new encoding
@parser = nil
end
end
encoding
end
def encoding
parser.encoding
rescue NoMethodError
nil
end
# Return whether parser result has errors related to encoding or not.
# false indicates just parser has no encoding errors, not encoding is valid.
def encoding_error?(parser=nil)
parser = self.parser unless parser
return false if parser.errors.empty?
parser.errors.any? do |error|
error.message.scrub =~ /(indicate\ encoding)|
(Invalid\ bytes)|
(Invalid\ char)|
(input\ conversion\ failed)/x
end
end
def parser
return @parser if @parser
return unless @body
url = @uri && @uri.to_s
if @encoding
@parser = mech.html_parser.parse html_body, url, @encoding
elsif mech.force_default_encoding
@parser = mech.html_parser.parse html_body, url, @mech.default_encoding
else
@encodings.reverse_each do |encoding|
@parser = mech.html_parser.parse html_body, url, encoding
break unless encoding_error? @parser
end
end
@parser
end
alias :root :parser
def pretty_print(q) # :nodoc:
q.object_group(self) {
q.breakable
q.group(1, '{url', '}') {q.breakable; q.pp uri }
q.breakable
q.group(1, '{meta_refresh', '}') {
meta_refresh.each { |link| q.breakable; q.pp link }
}
q.breakable
q.group(1, '{title', '}') { q.breakable; q.pp title }
q.breakable
q.group(1, '{iframes', '}') {
iframes.each { |link| q.breakable; q.pp link }
}
q.breakable
q.group(1, '{frames', '}') {
frames.each { |link| q.breakable; q.pp link }
}
q.breakable
q.group(1, '{links', '}') {
links.each { |link| q.breakable; q.pp link }
}
q.breakable
q.group(1, '{forms', '}') {
forms.each { |form| q.breakable; q.pp form }
}
}
end
alias inspect pretty_inspect # :nodoc:
def reset
@bases = nil
@forms = nil
@frames = nil
@iframes = nil
@links = nil
@labels = nil
@labels_hash = nil
@meta_refresh = nil
@parser = nil
@title = nil
end
# Return the canonical URI for the page if there is a link tag
# with href="canonical".
def canonical_uri
link = at('link[@rel="canonical"][@href]')
return unless link
href = link['href']
URI href
rescue URI::InvalidURIError
URI Mechanize::Util.uri_escape href
end
# Get the content type
def content_type
@meta_content_type || response['content-type']
end
##
# :method: search
#
# Shorthand for +parser.search+.
#
# See Nokogiri::XML::Node#search for details.
##
# :method: css
#
# Shorthand for +parser.css+.
#
# See also Nokogiri::XML::Node#css for details.
##
# :method: xpath
#
# Shorthand for +parser.xpath+.
#
# See also Nokogiri::XML::Node#xpath for details.
##
# :method: at
#
# Shorthand for +parser.at+.
#
# See also Nokogiri::XML::Node#at for details.
##
# :method: at_css
#
# Shorthand for +parser.at_css+.
#
# See also Nokogiri::XML::Node#at_css for details.
##
# :method: at_xpath
#
# Shorthand for +parser.at_xpath+.
#
# See also Nokogiri::XML::Node#at_xpath for details.
def_delegators :parser, :search, :css, :xpath, :at, :at_css, :at_xpath
alias / search
alias % at
##
# :method: form_with
#
# :call-seq:
# form_with(criteria)
# form_with(criteria) { |form| ... }
#
# Find a single form matching +criteria+. See +forms_with+ for
# details of +criteria+.
#
# Examples:
# page.form_with(action: '/post/login.php') do |f|
# ...
# end
##
# :method: form_with!(criteria)
#
# :call-seq:
# form_with!(criteria)
# form_with!(criteria) { |form| ... }
#
# Same as +form_with+ but raises an ElementNotFoundError if no button matches
# +criteria+
##
# :method: forms_with
#
# :call-seq:
# forms_with(name)
# forms_with(name: name_matcher, id: id_matcher, class: class_matcher,
# search: search_expression, xpath: xpath_expression, css: css_expression,
# action: action_matcher, ...)
#
# Find all forms form matching criteria. If a string is given, it
# is taken as a name attribute value. If a hash is given, forms
# are narrowed by the key-value pairs as follows.
#
# :id, :dom_id: selects forms with a #dom_id value that matches this
# value.
#
# :class, :dom_class: selects forms with a #dom_class value that
# matches this value. Note that class attribute values are compared
# literally as string, so forms_with(class: "a") does not match a
# form with class="a b". Use forms_with(css: "form.a") instead.
#
# :search: only selects forms matching this selector expression.
#
# :xpath: only selects forms matching this XPath expression.
#
# :css: only selects forms matching this CSS selector expression.
#
# :action, :method, etc.: narrows forms by a given attribute value
# using the === operator.
#
# Example:
# page.forms_with(css: '#content table.login_box form', method: /\APOST\z/i, ).each do |f|
# ...
# end
elements_with :form
##
# :method: link_with
#
# :call-seq:
# link_with(criteria)
# link_with(criteria) { |link| ... }
#
# Find a single link matching +criteria+. See +forms_with+ for
# details of +criteria+, where for "form(s)" read "link(s)".
#
# Example:
# page.link_with(href: /foo/).click
##
# :method: link_with!
#
# :call-seq:
# link_with!(criteria)
# link_with!(criteria) { |link| ... }
#
# Same as +link_with+ but raises an ElementNotFoundError if no button matches
# +criteria+
##
# :method: links_with
#
# :call-seq:
# links_with(criteria)
#
# Find all links matching +criteria+. See +forms_with+ for details
# of +criteria+, where for "form(s)" read "link(s)".
#
# Example:
# page.links_with(href: /foo/).each do |link|
# puts link.href
# end
elements_with :link
##
# :method: base_with
#
# :call-seq:
# base_with(criteria)
# base_with(criteria) { |base| ... }
#
# Find a single base tag matching +criteria+. See +forms_with+ for
# details of +criteria+, where for "form(s)" read "base tag(s)".
#
# Example:
# page.base_with(href: /foo/).click
##
# :method: base_with!(criteria)
#
# :call-seq:
# base_with!(criteria)
# base_with!(criteria) { |base| ... }
#
# Same as +base_with+ but raises an ElementNotFoundError if no button matches
# +criteria+
##
# :method: bases_with
#
# :call-seq: bases_with(criteria)
#
# Find all base tags matching +criteria+. See +forms_with+ for
# details of +criteria+, where for "form(s)" read "base tag(s)".
#
# Example:
# page.bases_with(href: /foo/).each do |base|
# puts base.href
# end
elements_with :base
##
# :method: frame_with
#
# :call-seq:
# frame_with(criteria)
# frame_with(criteria) { |frame| ... }
#
# Find a single frame tag matching +criteria+. See +forms_with+ for
# details of +criteria+, where for "form(s)" read "frame tag(s)".
#
# Example:
# page.frame_with(src: /foo/).click
##
# :method: frame_with!
#
# :call-seq:
# frame_with!(criteria)
# frame_with!(criteria) { |frame| ... }
#
# Same as +frame_with+ but raises an ElementNotFoundError if no button matches
# +criteria+
##
# :method: frames_with
#
# :call-seq: frames_with(criteria)
#
# Find all frame tags matching +criteria+. See +forms_with+ for
# details of +criteria+, where for "form(s)" read "frame tag(s)".
#
# Example:
# page.frames_with(src: /foo/).each do |frame|
# p frame.src
# end
elements_with :frame
##
# :method: iframe_with
#
# :call-seq:
# iframe_with(criteria)
# iframe_with(criteria) { |iframe| ... }
#
# Find a single iframe tag matching +criteria+. See +forms_with+ for
# details of +criteria+, where for "form(s)" read "iframe tag(s)".
#
# Example:
# page.iframe_with(src: /foo/).click
##
# :method: iframe_with!
#
# :call-seq:
# iframe_with!(criteria)
# iframe_with!(criteria) { |iframe| ... }
#
# Same as +iframe_with+ but raises an ElementNotFoundError if no button
# matches +criteria+
##
# :method: iframes_with
#
# :call-seq: iframes_with(criteria)
#
# Find all iframe tags matching +criteria+. See +forms_with+ for
# details of +criteria+, where for "form(s)" read "iframe tag(s)".
#
# Example:
# page.iframes_with(src: /foo/).each do |iframe|
# p iframe.src
# end
elements_with :iframe
##
# :method: image_with
#
# :call-seq:
# image_with(criteria)
# image_with(criteria) { |image| ... }
#
# Find a single image matching +criteria+. See +forms_with+ for
# details of +criteria+, where for "form(s)" read "image(s)".
#
# Example:
# page.image_with(alt: /main/).fetch.save
##
# :method: image_with!
#
# :call-seq:
# image_with!(criteria)
# image_with!(criteria) { |image| ... }
#
# Same as +image_with+ but raises an ElementNotFoundError if no button matches
# +criteria+
##
# :method: images_with
#
# :call-seq: images_with(criteria)
#
# Find all images matching +criteria+. See +forms_with+ for
# details of +criteria+, where for "form(s)" read "image(s)".
#
# Example:
# page.images_with(src: /jpg\Z/).each do |img|
# img.fetch.save
# end
elements_with :image
##
# Return a list of all link and area tags
def links
@links ||= %w{ a area }.map do |tag|
search(tag).map do |node|
Link.new(node, @mech, self)
end
end.flatten
end
##
# Return a list of all form tags
def forms
@forms ||= search('form').map do |html_form|
form = Mechanize::Form.new(html_form, @mech, self)
form.action ||= @uri.to_s
form
end
end
##
# Return a list of all meta refresh elements
def meta_refresh
query = @mech.follow_meta_refresh == :anywhere ? 'meta' : 'head > meta'
@meta_refresh ||= search(query).map do |node|
MetaRefresh.from_node node, self
end.compact
end
##
# Return a list of all base tags
def bases
@bases ||=
search('base').map { |node| Base.new(node, @mech, self) }
end
##
# Return a list of all frame tags
def frames
@frames ||=
search('frame').map { |node| Frame.new(node, @mech, self) }
end
##
# Return a list of all iframe tags
def iframes
@iframes ||=
search('iframe').map { |node| Frame.new(node, @mech, self) }
end
##
# Return a list of all img tags
def images
@images ||=
search('img').map { |node| Image.new(node, self) }
end
def image_urls
@image_urls ||= images.map(&:url).uniq
end
##
# Return a list of all label tags
def labels
@labels ||=
search('label').map { |node| Label.new(node, self) }
end
def labels_hash
unless @labels_hash
hash = {}
labels.each do |label|
hash[label.node['for']] = label if label.for
end
@labels_hash = hash
end
return @labels_hash
end
class << self
def charset content_type
charset = content_type[/;(?:\s*,)?\s*charset\s*=\s*([^()<>@,;:\\\"\/\[\]?={}\s]+)/i, 1]
return nil if charset == 'none'
charset
end
alias charset_from_content_type charset
end
def self.response_header_charset response
charsets = []
response.each do |header, value|
next unless header == 'content-type'
next unless value =~ /charset/i
charsets << charset(value)
end
charsets
end
##
# Retrieves all charsets from +meta+ tags in +body+
def self.meta_charset body
# HACK use .map
body.scan(//i).map do |meta|
if meta =~ /charset\s*=\s*(["'])?\s*(.+)\s*\1/i then
$2
elsif meta =~ /http-equiv\s*=\s*(["'])?content-type\1/i then
meta =~ /content\s*=\s*(["'])?(.*?)\1/i
m_charset = charset $2 if $2
m_charset if m_charset
end
end.compact
end
##
# Retrieves the last content-type set by a +meta+ tag in +body+
def self.meta_content_type body
body.scan(//i).reverse.map do |meta|
if meta =~ /http-equiv\s*=\s*(["'])?content-type\1/i then
meta =~ /content=(["'])?(.*?)\1/i
return $2
end
end
nil
end
private
def html_body
if @body
@body.empty? ? '' : @body
else
''
end
end
end
require 'mechanize/headers'
require 'mechanize/page/image'
require 'mechanize/page/label'
require 'mechanize/page/link'
require 'mechanize/page/base'
require 'mechanize/page/frame'
require 'mechanize/page/meta_refresh'
mechanize-2.10.1/lib/mechanize/file.rb 0000644 0000041 0000041 00000004422 14645745627 017571 0 ustar www-data www-data # frozen_string_literal: true
##
# This is the base class for the Pluggable Parsers. If Mechanize cannot find
# an appropriate class to use for the content type, this class will be used.
# For example, if you download an image/jpeg, Mechanize will not know how to
# parse it, so this class will be instantiated.
#
# This is a good class to use as the base class for building your own
# pluggable parsers.
#
# == Example
#
# require 'mechanize'
#
# agent = Mechanize.new
# agent.get('http://example.com/foo.jpg').class #=> Mechanize::File
class Mechanize::File
include Mechanize::Parser
##
# The HTTP response body, the raw file contents
attr_accessor :body
##
# The filename for this file based on the content-disposition of the
# response or the basename of the URL
attr_accessor :filename
alias content body
##
# Creates a new file retrieved from the given +uri+ and +response+ object.
# The +body+ is the HTTP response body and +code+ is the HTTP status.
def initialize uri = nil, response = nil, body = nil, code = nil
@uri = uri
@body = body
@code = code
@full_path = false unless defined? @full_path
fill_header response
extract_filename
yield self if block_given?
end
##
# Use this method to save the content of this object to +filename+.
# returns the filename
#
# file.save 'index.html'
# file.save 'index.html' # saves to index.html.1
#
# uri = URI 'http://localhost/test.html'
# file = Mechanize::File.new uri, nil, ''
# filename = file.save # saves to test.html
# puts filename # test.html
def save filename = nil
filename = find_free_name filename
save! filename
end
alias save_as save
##
# Use this method to save the content of this object to +filename+.
# This method will overwrite any existing filename that exists with the
# same name.
# returns the filename
#
# file.save 'index.html'
# file.save! 'index.html' # overwrite original file
# filename = file.save! 'index.html' # overwrite original file with filename 'index.html'
def save! filename = nil
filename ||= @filename
dirname = File.dirname filename
FileUtils.mkdir_p dirname
::File.open(filename, 'wb')do |f|
f.write body
end
filename
end
end
mechanize-2.10.1/lib/mechanize/version.rb 0000644 0000041 0000041 00000000107 14645745627 020333 0 ustar www-data www-data # frozen_string_literal: true
class Mechanize
VERSION = "2.10.1"
end
mechanize-2.10.1/lib/mechanize/file_connection.rb 0000644 0000041 0000041 00000000663 14645745627 022013 0 ustar www-data www-data # frozen_string_literal: true
##
# Wrapper to make a file URI work like an http URI
class Mechanize::FileConnection
@instance = nil
def self.new *a
@instance ||= super
end
def request uri, request
file_path = uri.select(:host, :path)
.select { |part| part && (part.length > 0) }
.join(":")
yield Mechanize::FileResponse.new(Mechanize::Util.uri_unescape(file_path))
end
end
mechanize-2.10.1/lib/mechanize/redirect_not_get_or_head_error.rb 0000644 0000041 0000041 00000001100 14645745627 025052 0 ustar www-data www-data # frozen_string_literal: true
##
# Raised when a POST, PUT, or DELETE request results in a redirect
# see RFC 2616 10.3.2, 10.3.3 http://www.ietf.org/rfc/rfc2616.txt
class Mechanize::RedirectNotGetOrHeadError < Mechanize::Error
attr_reader :page, :response_code, :verb, :uri
def initialize(page, verb)
@page = page
@verb = verb
@uri = page.uri
@response_code = page.code
end
def to_s
method = @verb.to_s.upcase
"#{@response_code} redirect received after a #{method} request"
end
alias :inspect :to_s
end
mechanize-2.10.1/lib/mechanize/http/ 0000755 0000041 0000041 00000000000 14645745627 017302 5 ustar www-data www-data mechanize-2.10.1/lib/mechanize/http/auth_store.rb 0000644 0000041 0000041 00000006016 14645745627 022007 0 ustar www-data www-data # frozen_string_literal: true
##
# A credential store for HTTP authentication.
#
# uri = URI 'http://example'
#
# store = Mechanize::HTTP::AuthStore.new
# store.add_auth uri, 'user1', 'pass'
# store.add_auth uri, 'user2', 'pass', 'realm'
#
# user, pass = store.credentials_for uri, 'realm' #=> 'user2', 'pass'
# user, pass = store.credentials_for uri, 'other' #=> 'user1', 'pass'
#
# store.remove_auth uri # removes all credentials
class Mechanize::HTTP::AuthStore
attr_reader :auth_accounts # :nodoc:
attr_reader :default_auth # :nodoc:
##
# Creates a new AuthStore
def initialize
@auth_accounts = Hash.new do |h, uri|
h[uri] = {}
end
@default_auth = nil
end
##
# Adds credentials +user+, +pass+ for the server at +uri+. If +realm+ is
# set the credentials are used only for that realm. If +realm+ is not set
# the credentials become the default for any realm on that URI.
#
# +domain+ and +realm+ are exclusive as NTLM does not follow RFC
# 2617. If +domain+ is given it is only used for NTLM authentication.
def add_auth uri, user, pass, realm = nil, domain = nil
uri = URI uri unless URI === uri
raise ArgumentError,
'NTLM domain given with realm which NTLM does not use' if
realm and domain
uri += '/'
auth_accounts[uri][realm] = [user, pass, domain]
self
end
##
# USE OF add_default_auth IS NOT RECOMMENDED AS IT MAY EXPOSE PASSWORDS TO
# THIRD PARTIES
#
# Adds credentials +user+, +pass+ as the default authentication credentials.
# If no other credentials are available these will be returned from
# credentials_for.
#
# If +domain+ is given it is only used for NTLM authentication.
def add_default_auth user, pass, domain = nil
warn <<-WARN
You have supplied default authentication credentials that apply to ANY SERVER.
Your username and password can be retrieved by ANY SERVER using Basic
authentication.
THIS EXPOSES YOUR USERNAME AND PASSWORD TO DISCLOSURE WITHOUT YOUR KNOWLEDGE.
Use add_auth to set authentication credentials that will only be delivered
only to a particular server you specify.
WARN
@default_auth = [user, pass, domain]
end
##
# Returns true if credentials exist for the +challenges+ from the server at
# +uri+.
def credentials? uri, challenges
challenges.any? do |challenge|
credentials_for uri, challenge.realm_name
end
end
##
# Retrieves credentials for +realm+ on the server at +uri+.
def credentials_for uri, realm
uri = URI uri unless URI === uri
uri += '/'
uri.user = nil
uri.password = nil
realms = @auth_accounts[uri]
realms[realm] || realms[nil] || @default_auth
end
##
# Removes credentials for +realm+ on the server at +uri+. If +realm+ is not
# set all credentials for the server at +uri+ are removed.
def remove_auth uri, realm = nil
uri = URI uri unless URI === uri
uri += '/'
if realm then
auth_accounts[uri].delete realm
else
auth_accounts.delete uri
end
self
end
end
mechanize-2.10.1/lib/mechanize/http/www_authenticate_parser.rb 0000644 0000041 0000041 00000006457 14645745627 024601 0 ustar www-data www-data # frozen_string_literal: true
require 'strscan'
##
# Parses the WWW-Authenticate HTTP header into separate challenges.
class Mechanize::HTTP::WWWAuthenticateParser
attr_accessor :scanner # :nodoc:
##
# Creates a new header parser for WWW-Authenticate headers
def initialize
@scanner = nil
end
##
# Parsers the header. Returns an Array of challenges as strings
def parse www_authenticate
challenges = []
@scanner = StringScanner.new www_authenticate
while true do
break if @scanner.eos?
start = @scanner.pos
challenge = Mechanize::HTTP::AuthChallenge.new
scheme = auth_scheme
if scheme == 'Negotiate'
scan_comma_spaces
end
break unless scheme
challenge.scheme = scheme
space = spaces
if scheme == 'NTLM' then
if space then
challenge.params = @scanner.scan(/.*/)
end
challenge.raw = www_authenticate[start, @scanner.pos]
challenges << challenge
next
else
scheme.capitalize!
end
next unless space
params = {}
while true do
pos = @scanner.pos
name, value = auth_param
name.downcase! if name =~ /^realm$/i
unless name then
challenge.params = params
challenges << challenge
if @scanner.eos? then
challenge.raw = www_authenticate[start, @scanner.pos]
break
end
@scanner.pos = pos # rewind
challenge.raw = www_authenticate[start, @scanner.pos].sub(/(,+)? *$/, '')
challenge = nil # a token should be next, new challenge
break
else
params[name] = value
end
spaces
@scanner.scan(/(, *)+/)
end
end
challenges
end
##
# 1*SP
#
# Parses spaces
def spaces
@scanner.scan(/ +/)
end
##
# scans a comma followed by spaces
# needed for Negotiation, NTLM
#
def scan_comma_spaces
@scanner.scan(/, +/)
end
##
# token = 1*
#
# Parses a token
def token
@scanner.scan(/[^\000-\037\177()<>@,;:\\"\/\[\]?={} ]+/)
end
##
# auth-scheme = token
#
# Parses an auth scheme (a token)
alias auth_scheme token
##
# auth-param = token "=" ( token | quoted-string )
#
# Parses an auth parameter
def auth_param
return nil unless name = token
return nil unless @scanner.scan(/ *= */)
value = if @scanner.peek(1) == '"' then
quoted_string
else
token
end
return nil unless value
return name, value
end
##
# quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
# qdtext = >
# quoted-pair = "\" CHAR
#
# For TEXT, the rules of RFC 2047 are ignored.
def quoted_string
return nil unless @scanner.scan(/"/)
text = String.new
while true do
chunk = @scanner.scan(/[\r\n \t\x21\x23-\x7e\u0080-\u00ff]+/) # not " which is \x22
if chunk then
text << chunk
text << @scanner.get_byte if
chunk.end_with? '\\' and '"' == @scanner.peek(1)
else
if '"' == @scanner.peek(1) then
@scanner.get_byte
break
else
return nil
end
end
end
text
end
end
mechanize-2.10.1/lib/mechanize/http/agent.rb 0000644 0000041 0000041 00000107015 14645745627 020731 0 ustar www-data www-data # frozen_string_literal: true
require 'tempfile'
require 'net/ntlm'
require 'webrobots'
##
# An HTTP (and local disk access) user agent. This class is an implementation
# detail and is subject to change at any time.
class Mechanize::HTTP::Agent
CREDENTIAL_HEADERS = ['Authorization']
COOKIE_HEADERS = ['Cookie']
POST_HEADERS = ['Content-Length', 'Content-MD5', 'Content-Type']
# :section: Headers
# Disables If-Modified-Since conditional requests (enabled by default)
attr_accessor :conditional_requests
# Is gzip compression of requests enabled?
attr_accessor :gzip_enabled
# A hash of request headers to be used for every request
attr_accessor :request_headers
# The User-Agent header to send
attr_reader :user_agent
# :section: History
# history of requests made
attr_accessor :history
# :section: Hooks
# A list of hooks to call after retrieving a response. Hooks are called with
# the agent and the response returned.
attr_reader :post_connect_hooks
# A list of hooks to call before making a request. Hooks are called with
# the agent and the request to be performed.
attr_reader :pre_connect_hooks
# A list of hooks to call to handle the content-encoding of a request.
attr_reader :content_encoding_hooks
# :section: HTTP Authentication
attr_reader :auth_store # :nodoc:
attr_reader :authenticate_methods # :nodoc:
attr_reader :digest_challenges # :nodoc:
# :section: Redirection
# Follow HTML meta refresh and HTTP Refresh. If set to +:anywhere+ meta
# refresh tags outside of the head element will be followed.
attr_accessor :follow_meta_refresh
# Follow an HTML meta refresh that has no "url=" in the content attribute.
#
# Defaults to false to prevent infinite refresh loops.
attr_accessor :follow_meta_refresh_self
# Controls how this agent deals with redirects. The following values are
# allowed:
#
# :all, true:: All 3xx redirects are followed (default)
# :permanent:: Only 301 Moved Permanently redirects are followed
# false:: No redirects are followed
attr_accessor :redirect_ok
# Maximum number of redirects to follow
attr_accessor :redirection_limit
# :section: Allowed error codes
# List of error codes (in String or Integer) to handle without
# raising Mechanize::ResponseCodeError, defaulted to an empty array.
# Note that 2xx, 3xx and 401 status codes will be handled without
# checking this list.
attr_accessor :allowed_error_codes
# :section: Robots
# When true, this agent will consult the site's robots.txt for each access.
attr_reader :robots
# Mutex used when fetching robots.txt
attr_reader :robots_mutex
# :section: SSL
# OpenSSL key password
attr_accessor :pass
# :section: Timeouts
# Set to false to disable HTTP/1.1 keep-alive requests
attr_accessor :keep_alive
# Length of time to wait until a connection is opened in seconds
attr_accessor :open_timeout
# Length of time to attempt to read data from the server
attr_accessor :read_timeout
# :section:
# The cookies for this agent
attr_accessor :cookie_jar
# Responses larger than this will be written to a Tempfile instead of stored
# in memory. Setting this to nil disables creation of Tempfiles.
attr_accessor :max_file_buffer
# :section: Utility
# The context parses responses into pages
attr_accessor :context
attr_reader :http # :nodoc:
# When set to true mechanize will ignore an EOF during chunked transfer
# encoding so long as at least one byte was received. Be careful when
# enabling this as it may cause data loss.
attr_accessor :ignore_bad_chunking
# Handlers for various URI schemes
attr_accessor :scheme_handlers
# :section:
# Creates a new Mechanize HTTP user agent. The user agent is an
# implementation detail of mechanize and its API may change at any time.
# The connection_name can be used to segregate SSL connections.
# Agents with different names will not share the same persistent connection.
def initialize(connection_name = 'mechanize')
@allowed_error_codes = []
@conditional_requests = true
@context = nil
@content_encoding_hooks = []
@cookie_jar = Mechanize::CookieJar.new
@follow_meta_refresh = false
@follow_meta_refresh_self = false
@gzip_enabled = true
@history = Mechanize::History.new
@ignore_bad_chunking = false
@keep_alive = true
@max_file_buffer = 100_000 # 5MB for response bodies
@open_timeout = nil
@post_connect_hooks = []
@pre_connect_hooks = []
@read_timeout = nil
@redirect_ok = true
@redirection_limit = 20
@request_headers = {}
@robots = false
@robots_mutex = Mutex.new
@user_agent = nil
@webrobots = nil
# HTTP Authentication
@auth_store = Mechanize::HTTP::AuthStore.new
@authenticate_parser = Mechanize::HTTP::WWWAuthenticateParser.new
@authenticate_methods = Hash.new do |methods, uri|
methods[uri] = Hash.new do |realms, auth_scheme|
realms[auth_scheme] = []
end
end
@digest_auth = Net::HTTP::DigestAuth.new
@digest_challenges = {}
# SSL
@pass = nil
@scheme_handlers = Hash.new { |h, scheme|
h[scheme] = lambda { |link, page|
raise Mechanize::UnsupportedSchemeError.new(scheme, link)
}
}
@scheme_handlers['http'] = lambda { |link, page| link }
@scheme_handlers['https'] = @scheme_handlers['http']
@scheme_handlers['relative'] = @scheme_handlers['http']
@scheme_handlers['file'] = @scheme_handlers['http']
@http =
if defined?(Net::HTTP::Persistent::DEFAULT_POOL_SIZE)
Net::HTTP::Persistent.new(name: connection_name)
else
# net-http-persistent < 3.0
Net::HTTP::Persistent.new(connection_name)
end
@http.idle_timeout = 5
@http.keep_alive = 300
end
##
# Adds credentials +user+, +pass+ for +uri+. If +realm+ is set the
# credentials are used only for that realm. If +realm+ is not set the
# credentials become the default for any realm on that URI.
#
# +domain+ and +realm+ are exclusive as NTLM does not follow RFC 2617. If
# +domain+ is given it is only used for NTLM authentication.
def add_auth uri, user, password, realm = nil, domain = nil
@auth_store.add_auth uri, user, password, realm, domain
end
##
# USE OF add_default_auth IS NOT RECOMMENDED AS IT MAY EXPOSE PASSWORDS TO
# THIRD PARTIES
#
# Adds credentials +user+, +pass+ as the default authentication credentials.
# If no other credentials are available these will be returned from
# credentials_for.
#
# If +domain+ is given it is only used for NTLM authentication.
def add_default_auth user, password, domain = nil # :nodoc:
@auth_store.add_default_auth user, password, domain
end
##
# Retrieves +uri+ and parses it into a page or other object according to
# PluggableParser. If the URI is an HTTP or HTTPS scheme URI the given HTTP
# +method+ is used to retrieve it, along with the HTTP +headers+, request
# +params+ and HTTP +referer+.
#
# The final URI to access is built with +uri+ and +params+, the
# latter of which is formatted into a string using
# Mechanize::Util.build_query_string, which see.
#
# +redirects+ tracks the number of redirects experienced when retrieving the
# page. If it is over the redirection_limit an error will be raised.
def fetch uri, method = :get, headers = {}, params = [],
referer = current_page, redirects = 0
referer_uri = referer ? referer.uri : nil
uri = resolve uri, referer
uri, params = resolve_parameters uri, method, params
request = http_request uri, method, params
connection = connection_for uri
request_auth request, uri
disable_keep_alive request
enable_gzip request
request_language_charset request
request_cookies request, uri
request_host request, uri
request_referer request, uri, referer_uri
request_user_agent request
request_add_headers request, headers
pre_connect request
# Consult robots.txt
if robots && uri.is_a?(URI::HTTP)
robots_allowed?(uri) or raise Mechanize::RobotsDisallowedError.new(uri)
end
# Add If-Modified-Since if page is in history
if page = visited_page(uri) and last_modified = page.response['Last-Modified']
request['If-Modified-Since'] = last_modified
end if @conditional_requests
# Specify timeouts if supplied and our connection supports them
if @open_timeout && connection.respond_to?(:open_timeout=)
connection.open_timeout = @open_timeout
end
if @read_timeout && connection.respond_to?(:read_timeout=)
connection.read_timeout = @read_timeout
end
request_log request
response_body_io = nil
# Send the request
begin
response = connection.request(uri, request) { |res|
response_log res
response_body_io = response_read res, request, uri
res
}
rescue Mechanize::ChunkedTerminationError => e
raise unless @ignore_bad_chunking
response = e.response
response_body_io = e.body_io
end
hook_content_encoding response, uri, response_body_io
response_body_io = response_content_encoding response, response_body_io if
request.response_body_permitted?
post_connect uri, response, response_body_io
page = response_parse response, response_body_io, uri
response_cookies response, uri, page
meta = response_follow_meta_refresh response, uri, page, redirects
return meta if meta
if robots && page.is_a?(Mechanize::Page)
page.parser.noindex? and raise Mechanize::RobotsDisallowedError.new(uri)
end
case response
when Net::HTTPSuccess
page
when Mechanize::FileResponse
page
when Net::HTTPNotModified
log.debug("Got cached page") if log
visited_page(uri) || page
when Net::HTTPRedirection
response_redirect response, method, page, redirects, headers, referer
when Net::HTTPUnauthorized
response_authenticate(response, page, uri, request, headers, params,
referer)
else
if @allowed_error_codes.any? {|code| code.to_s == page.code} then
page
else
raise Mechanize::ResponseCodeError.new(page, 'unhandled response')
end
end
end
# URI for a proxy connection
def proxy_uri
@http.proxy_uri
end
# Retry non-idempotent requests?
def retry_change_requests
@http.retry_change_requests
end
# Retry non-idempotent requests
def retry_change_requests= retri
@http.retry_change_requests = retri
end
# :section: Headers
def user_agent= user_agent
@webrobots = nil if user_agent != @user_agent
@user_agent = user_agent
end
# :section: History
# Equivalent to the browser back button. Returns the most recent page
# visited.
def back
@history.pop
end
##
# Returns the latest page loaded by the agent
def current_page
@history.last
end
# Returns the maximum size for the history stack.
def max_history
@history.max_size
end
# Set the maximum size for the history stack.
def max_history=(length)
@history.max_size = length
end
# Returns a visited page for the url passed in, otherwise nil
def visited_page url
@history.visited_page resolve url
end
# :section: Hooks
def hook_content_encoding response, uri, response_body_io
@content_encoding_hooks.each do |hook|
hook.call self, uri, response, response_body_io
end
end
##
# Invokes hooks added to post_connect_hooks after a +response+ is returned
# and the response +body+ is handled.
#
# Yields the +context+, the +uri+ for the request, the +response+ and the
# response +body+.
def post_connect uri, response, body_io # :yields: agent, uri, response, body
@post_connect_hooks.each do |hook|
begin
hook.call self, uri, response, body_io.read
ensure
body_io.rewind
end
end
end
##
# Invokes hooks added to pre_connect_hooks before a +request+ is made.
# Yields the +agent+ and the +request+ that will be performed to each hook.
def pre_connect request # :yields: agent, request
@pre_connect_hooks.each do |hook|
hook.call self, request
end
end
# :section: Request
def connection_for uri
case uri.scheme.downcase
when 'http', 'https' then
return @http
when 'file' then
return Mechanize::FileConnection.new
end
end
# Closes all open connections for this agent.
def shutdown
http.shutdown
end
##
# Decodes a gzip-encoded +body_io+. If it cannot be decoded, inflate is
# tried followed by raising an error.
def content_encoding_gunzip body_io
log.debug('gzip response') if log
zio = Zlib::GzipReader.new body_io
out_io = auto_io 'mechanize-gunzip', 16384, zio
zio.finish
return out_io
rescue Zlib::Error => gz_error
log.warn "unable to gunzip response: #{gz_error} (#{gz_error.class})" if
log
body_io.rewind
body_io.read 10
begin
log.warn "trying raw inflate on response" if log
return inflate body_io, -Zlib::MAX_WBITS
rescue Zlib::Error => e
log.error "unable to inflate response: #{e} (#{e.class})" if log
raise
end
ensure
# do not close a second time if we failed the first time
zio.close if zio and !(zio.closed? or gz_error)
body_io.close unless body_io.closed?
end
##
# Decodes a deflate-encoded +body_io+. If it cannot be decoded, raw inflate
# is tried followed by raising an error.
def content_encoding_inflate body_io
log.debug('deflate body') if log
return inflate body_io
rescue Zlib::Error
log.error('unable to inflate response, trying raw deflate') if log
body_io.rewind
begin
return inflate body_io, -Zlib::MAX_WBITS
rescue Zlib::Error => e
log.error("unable to inflate response: #{e}") if log
raise
end
ensure
body_io.close
end
def disable_keep_alive request
request['connection'] = 'close' unless @keep_alive
end
def enable_gzip request
request['accept-encoding'] = if @gzip_enabled
'gzip,deflate,identity'
else
'identity'
end
end
def http_request uri, method, params = nil
case uri.scheme.downcase
when 'http', 'https' then
klass = Net::HTTP.const_get(method.to_s.capitalize)
request ||= klass.new(uri.request_uri)
request.body = params.first if params
request
when 'file' then
Mechanize::FileRequest.new uri
end
end
def request_add_headers request, headers = {}
@request_headers.each do |k,v|
request[k] = v
end
headers.each do |field, value|
case field
when :etag then request["ETag"] = value
when :if_modified_since then request["If-Modified-Since"] = value
when Symbol then
raise ArgumentError, "unknown header symbol #{field}"
else
request[field] = value
end
end
end
def request_auth request, uri
base_uri = uri + '/'
base_uri.user &&= nil
base_uri.password &&= nil
schemes = @authenticate_methods[base_uri]
if realm = schemes[:digest].find { |r| r.uri == base_uri } then
request_auth_digest request, uri, realm, base_uri, false
elsif realm = schemes[:iis_digest].find { |r| r.uri == base_uri } then
request_auth_digest request, uri, realm, base_uri, true
elsif realm = schemes[:basic].find { |r| r.uri == base_uri } then
user, password, = @auth_store.credentials_for uri, realm.realm
request.basic_auth user, password
end
end
def request_auth_digest request, uri, realm, base_uri, iis
challenge = @digest_challenges[realm]
uri.user, uri.password, = @auth_store.credentials_for uri, realm.realm
auth = @digest_auth.auth_header uri, challenge.to_s, request.method, iis
request['Authorization'] = auth
end
def request_cookies request, uri
return if @cookie_jar.empty? uri
cookies = @cookie_jar.cookies uri
return if cookies.empty?
request.add_field 'Cookie', cookies.join('; ')
end
def request_host request, uri
port = [80, 443].include?(uri.port.to_i) ? nil : uri.port
host = uri.host
request['Host'] = [host, port].compact.join ':'
end
def request_language_charset request
request['accept-charset'] = 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'
request['accept-language'] = 'en-us,en;q=0.5'
end
# Log specified headers for the request
def request_log request
return unless log
log.info("#{request.class}: #{request.path}")
request.each_header do |k, v|
log.debug("request-header: #{k} => #{v}")
end
end
# Sets a Referer header. Fragment part is removed as demanded by
# RFC 2616 14.36, and user information part is removed just like
# major browsers do.
def request_referer request, uri, referer
return unless referer
return if 'https'.casecmp(referer.scheme) == 0 and
'https'.casecmp(uri.scheme) != 0
if referer.fragment || referer.user || referer.password
referer = referer.dup
referer.fragment = referer.user = referer.password = nil
end
request['Referer'] = referer
end
def request_user_agent request
request['User-Agent'] = @user_agent if @user_agent
end
def resolve(uri, referer = current_page)
referer_uri = referer && referer.uri
if uri.is_a?(URI)
uri = uri.dup
elsif uri.nil?
if referer_uri
return referer_uri
end
raise ArgumentError, "absolute URL needed (not nil)"
else
url = uri.to_s.strip
if url.empty?
if referer_uri
return referer_uri.dup.tap { |u| u.fragment = nil }
end
raise ArgumentError, "absolute URL needed (not #{uri.inspect})"
end
url.gsub!(/[^#{0.chr}-#{126.chr}]/o) { |match|
Mechanize::Util.uri_escape(match)
}
escaped_url = Mechanize::Util.html_unescape(
url.split(/((?:%[0-9A-Fa-f]{2})+|#)/).each_slice(2).map { |x, y|
"#{WEBrick::HTTPUtils.escape(x)}#{y}"
}.join('')
)
begin
uri = URI.parse(escaped_url)
rescue
uri = URI.parse(WEBrick::HTTPUtils.escape(escaped_url))
end
end
uri.host = referer_uri.host if referer_uri && URI::HTTP === uri && uri.host.nil?
scheme = uri.relative? ? 'relative' : uri.scheme.downcase
uri = @scheme_handlers[scheme].call(uri, referer)
if uri.relative?
raise ArgumentError, "absolute URL needed (not #{uri})" unless
referer_uri
if referer.respond_to?(:bases) && referer.parser &&
(lbase = referer.bases.last) && lbase.uri && lbase.uri.absolute?
base = lbase
else
base = nil
end
base = referer_uri + (base ? base.uri : referer_uri)
# Workaround for URI's bug in that it squashes consecutive
# slashes. See #304.
if uri.path.match(%r{\A(.*?/)(?!/\.\.?(?!/))(/.*)\z}i)
uri = URI((base + $1).to_s + $2)
else
uri = base + uri
end
# Strip initial "/.." bits from the path
uri.path.sub!(/^(\/\.\.)+(?=\/)/, '')
end
unless ['http', 'https', 'file'].include?(uri.scheme.downcase)
raise ArgumentError, "unsupported scheme: #{uri.scheme}"
end
case uri.path
when nil
raise ArgumentError, "hierarchical URL needed (not #{uri})"
when ''.freeze
uri.path = '/'
end
uri
end
def secure_resolve!(uri, referer = current_page)
new_uri = resolve(uri, referer)
if (referer_uri = referer && referer.uri) &&
referer_uri.scheme != 'file'.freeze &&
new_uri.scheme == 'file'.freeze
raise Mechanize::Error, "insecure redirect to a file URI"
end
new_uri
end
def resolve_parameters uri, method, parameters
case method
when :head, :get, :delete, :trace then
if parameters and parameters.length > 0
uri.query ||= ''
uri.query << '&' if uri.query.length > 0
uri.query << Mechanize::Util.build_query_string(parameters)
end
return uri, nil
end
return uri, parameters
end
# :section: Response
def get_meta_refresh response, uri, page
return nil unless @follow_meta_refresh
if page.respond_to?(:meta_refresh) and
(redirect = page.meta_refresh.first) then
[redirect.delay, redirect.href] unless
not @follow_meta_refresh_self and redirect.link_self
elsif refresh = response['refresh']
delay, href, link_self = Mechanize::Page::MetaRefresh.parse refresh, uri
raise Mechanize::Error, 'Invalid refresh http header' unless delay
[delay.to_f, href] unless
not @follow_meta_refresh_self and link_self
end
end
def response_authenticate(response, page, uri, request, headers, params,
referer)
www_authenticate = response['www-authenticate']
unless www_authenticate = response['www-authenticate'] then
message = 'WWW-Authenticate header missing in response'
raise Mechanize::UnauthorizedError.new(page, nil, message)
end
challenges = @authenticate_parser.parse www_authenticate
unless @auth_store.credentials? uri, challenges then
message = "no credentials found, provide some with #add_auth"
raise Mechanize::UnauthorizedError.new(page, challenges, message)
end
if challenge = challenges.find { |c| c.scheme =~ /^Digest$/i } then
realm = challenge.realm uri
auth_scheme = if response['server'] =~ /Microsoft-IIS/ then
:iis_digest
else
:digest
end
existing_realms = @authenticate_methods[realm.uri][auth_scheme]
if existing_realms.include? realm
message = 'Digest authentication failed'
raise Mechanize::UnauthorizedError.new(page, challenges, message)
end
existing_realms << realm
@digest_challenges[realm] = challenge
elsif challenge = challenges.find { |c| c.scheme == 'NTLM' } then
existing_realms = @authenticate_methods[uri + '/'][:ntlm]
if existing_realms.include?(realm) and not challenge.params then
message = 'NTLM authentication failed'
raise Mechanize::UnauthorizedError.new(page, challenges, message)
end
existing_realms << realm
if challenge.params then
type_2 = Net::NTLM::Message.decode64 challenge.params
user, password, domain = @auth_store.credentials_for uri, nil
type_3 = type_2.response({ :user => user, :password => password,
:domain => domain },
{ :ntlmv2 => true }).encode64
headers['Authorization'] = "NTLM #{type_3}"
else
type_1 = Net::NTLM::Message::Type1.new.encode64
headers['Authorization'] = "NTLM #{type_1}"
end
elsif challenge = challenges.find { |c| c.scheme == 'Basic' } then
realm = challenge.realm uri
existing_realms = @authenticate_methods[realm.uri][:basic]
if existing_realms.include? realm then
message = 'Basic authentication failed'
raise Mechanize::UnauthorizedError.new(page, challenges, message)
end
existing_realms << realm
else
message = 'unsupported authentication scheme'
raise Mechanize::UnauthorizedError.new(page, challenges, message)
end
fetch uri, request.method.downcase.to_sym, headers, params, referer
end
def response_content_encoding response, body_io
length = response.content_length ||
case body_io
when Tempfile, IO then
body_io.stat.size
else
body_io.length
end
return body_io if length.zero?
out_io = case response['Content-Encoding']
when nil, 'none', '7bit', 'identity', "" then
body_io
when 'deflate' then
content_encoding_inflate body_io
when 'gzip', 'x-gzip' then
content_encoding_gunzip body_io
else
raise Mechanize::Error,
"unsupported content-encoding: #{response['Content-Encoding']}"
end
out_io.flush
out_io.rewind
out_io
rescue Zlib::Error => e
message = String.new("error handling content-encoding #{response['Content-Encoding']}:")
message << " #{e.message} (#{e.class})"
raise Mechanize::Error, message
ensure
begin
if Tempfile === body_io and
(StringIO === out_io or (out_io and out_io.path != body_io.path)) then
body_io.close!
end
rescue IOError
# HACK ruby 1.8 raises IOError when closing the stream
end
end
def response_cookies response, uri, page
if Mechanize::Page === page and page.body =~ /Set-Cookie/n
page.search('//head/meta[@http-equiv="Set-Cookie"]').each do |meta|
save_cookies(uri, meta['content'])
end
end
header_cookies = response.get_fields 'Set-Cookie'
return unless header_cookies
header_cookies.each do |set_cookie|
save_cookies(uri, set_cookie)
end
end
def save_cookies(uri, set_cookie)
return [] if set_cookie.nil?
if log = log() # reduce method calls
@cookie_jar.parse(set_cookie, uri, :logger => log) { |c|
log.debug("saved cookie: #{c}")
true
}
else
@cookie_jar.parse(set_cookie, uri)
end
end
def response_follow_meta_refresh response, uri, page, redirects
delay, new_url = get_meta_refresh(response, uri, page)
return nil unless delay
new_url = new_url ? secure_resolve!(new_url, page) : uri
raise Mechanize::RedirectLimitReachedError.new(page, redirects) if
redirects + 1 > @redirection_limit
sleep delay
@history.push(page, page.uri)
fetch new_url, :get, {}, [],
Mechanize::Page.new, redirects + 1
end
def response_log response
return unless log
log.info("status: #{response.class} #{response.http_version} " \
"#{response.code} #{response.message}")
response.each_header do |k, v|
log.debug("response-header: #{k} => #{v}")
end
end
def response_parse response, body_io, uri
@context.parse uri, response, body_io
end
def response_read response, request, uri
content_length = response.content_length
if use_tempfile? content_length then
body_io = make_tempfile 'mechanize-raw'
else
body_io = StringIO.new.set_encoding(Encoding::BINARY)
end
total = 0
begin
response.read_body { |part|
total += part.length
if StringIO === body_io and use_tempfile? total then
new_io = make_tempfile 'mechanize-raw'
new_io.write body_io.string
body_io = new_io
end
body_io.write(part)
log.debug("Read #{part.length} bytes (#{total} total)") if log
}
rescue EOFError => e
# terminating CRLF might be missing, let the user check the document
raise unless response.chunked? and total.nonzero?
body_io.rewind
raise Mechanize::ChunkedTerminationError.new(e, response, body_io, uri,
@context)
rescue Net::HTTP::Persistent::Error, Errno::ECONNRESET => e
body_io.rewind
raise Mechanize::ResponseReadError.new(e, response, body_io, uri,
@context)
end
body_io.flush
body_io.rewind
raise Mechanize::ResponseCodeError.new(response, uri) if
Net::HTTPUnknownResponse === response
content_length = response.content_length
unless Net::HTTP::Head === request or Net::HTTPRedirection === response then
if content_length and content_length != body_io.length
err = EOFError.new("Content-Length (#{content_length}) does not " \
"match response body length (#{body_io.length})")
raise Mechanize::ResponseReadError.new(err, response, body_io, uri,
@context)
end
end
body_io
end
def response_redirect(response, method, page, redirects, headers,
referer = current_page)
case @redirect_ok
when true, :all
# shortcut
when false, nil
return page
when :permanent
return page unless Net::HTTPMovedPermanently === response
end
log.info("follow redirect to: #{response['Location']}") if log
raise Mechanize::RedirectLimitReachedError.new(page, redirects) if
redirects + 1 > @redirection_limit
redirect_method = method == :head ? :head : :get
new_uri = secure_resolve!(response['Location'].to_s, page)
@history.push(page, page.uri)
# Make sure we are not copying over the POST headers from the original request
POST_HEADERS.each do |key|
headers.delete_if { |h| h.casecmp?(key) }
end
# Make sure we clear credential headers if being redirected to another site
if new_uri.host == page.uri.host
if new_uri.port != page.uri.port
# https://datatracker.ietf.org/doc/html/rfc6265#section-8.5
# cookies are OK to be shared across ports on the same host
CREDENTIAL_HEADERS.each { |ch| headers.delete_if { |h| h.casecmp?(ch) } }
end
else
(COOKIE_HEADERS + CREDENTIAL_HEADERS).each { |ch| headers.delete_if { |h| h.casecmp?(ch) } }
end
fetch new_uri, redirect_method, headers, [], referer, redirects + 1
end
# :section: Robots
RobotsKey = :__mechanize_get_robots__
def get_robots(uri) # :nodoc:
robots_mutex.synchronize do
Thread.current[RobotsKey] = true
begin
fetch(uri).body
rescue Mechanize::ResponseCodeError => e
case e.response_code
when /\A4\d\d\z/
''
else
raise e
end
rescue Mechanize::RedirectLimitReachedError
''
ensure
Thread.current[RobotsKey] = false
end
end
end
def robots= value
require 'webrobots' if value
@webrobots = nil if value != @robots
@robots = value
end
##
# Tests if this agent is allowed to access +url+, consulting the site's
# robots.txt.
def robots_allowed? uri
return true if Thread.current[RobotsKey]
webrobots.allowed? uri
end
# Opposite of robots_allowed?
def robots_disallowed? url
!robots_allowed? url
end
# Returns an error object if there is an error in fetching or parsing
# robots.txt of the site +url+.
def robots_error(url)
webrobots.error(url)
end
# Raises the error if there is an error in fetching or parsing robots.txt of
# the site +url+.
def robots_error!(url)
webrobots.error!(url)
end
# Removes robots.txt cache for the site +url+.
def robots_reset(url)
webrobots.reset(url)
end
def webrobots
@webrobots ||= WebRobots.new(@user_agent, :http_get => method(:get_robots))
end
# :section: SSL
# Path to an OpenSSL CA certificate file
def ca_file
@http.ca_file
end
# Sets the path to an OpenSSL CA certificate file
def ca_file= ca_file
@http.ca_file = ca_file
end
# The SSL certificate store used for validating connections
def cert_store
@http.cert_store
end
# Sets the SSL certificate store used for validating connections
def cert_store= cert_store
@http.cert_store = cert_store
end
# The client X509 certificate
def certificate
@http.certificate
end
# Sets the client certificate to given X509 certificate. If a path is given
# the certificate will be loaded and set.
def certificate= certificate
certificate = if OpenSSL::X509::Certificate === certificate then
certificate
else
OpenSSL::X509::Certificate.new File.read certificate
end
@http.certificate = certificate
end
# An OpenSSL private key or the path to a private key
def private_key
@http.private_key
end
# Sets the client's private key
def private_key= private_key
private_key = if OpenSSL::PKey::PKey === private_key then
private_key
else
OpenSSL::PKey::RSA.new File.read(private_key), @pass
end
@http.private_key = private_key
end
# SSL version to use
def ssl_version
@http.ssl_version
end
# Sets the SSL version to use
def ssl_version= ssl_version
@http.ssl_version = ssl_version
end
# A callback for additional certificate verification. See
# OpenSSL::SSL::SSLContext#verify_callback
#
# The callback can be used for debugging or to ignore errors by always
# returning +true+. Specifying nil uses the default method that was valid
# when the SSLContext was created
def verify_callback
@http.verify_callback
end
# Sets the certificate verify callback
def verify_callback= verify_callback
@http.verify_callback = verify_callback
end
# How to verify SSL connections. Defaults to VERIFY_PEER
def verify_mode
@http.verify_mode
end
# Sets the mode for verifying SSL connections
def verify_mode= verify_mode
@http.verify_mode = verify_mode
end
# :section: Timeouts
# Reset connections that have not been used in this many seconds
def idle_timeout
@http.idle_timeout
end
# Sets the connection idle timeout for persistent connections
def idle_timeout= timeout
@http.idle_timeout = timeout
end
# :section: Utility
##
# Creates a new output IO by reading +input_io+ in +read_size+ chunks. If
# the output is over the max_file_buffer size a Tempfile with +name+ is
# created.
#
# If a block is provided, each chunk of +input_io+ is yielded for further
# processing.
def auto_io name, read_size, input_io
out_io = StringIO.new.set_encoding(Encoding::BINARY)
until input_io.eof? do
if StringIO === out_io and use_tempfile? out_io.size then
new_io = make_tempfile name
new_io.write out_io.string
out_io = new_io
end
chunk = input_io.read read_size
chunk = yield chunk if block_given?
out_io.write chunk
end
out_io.rewind
out_io
end
def inflate compressed, window_bits = nil
inflate = Zlib::Inflate.new window_bits
out_io = auto_io 'mechanize-inflate', 1024, compressed do |chunk|
inflate.inflate chunk
end
inflate.finish
out_io
ensure
inflate.close if inflate.finished?
end
def log
@context.log
end
##
# Sets the proxy address, port, user, and password. +addr+ may be
# an HTTP URL/URI or a host name, +port+ may be a port number, service
# name or port number string.
def set_proxy addr, port = nil, user = nil, pass = nil
case addr
when URI::HTTP
proxy_uri = addr.dup
when %r{\Ahttps?://}i
proxy_uri = URI addr
when String
proxy_uri = URI "http://#{addr}"
when nil
@http.proxy = nil
return
end
case port
when Integer
proxy_uri.port = port
when nil
else
begin
proxy_uri.port = Socket.getservbyname port
rescue SocketError
begin
proxy_uri.port = Integer port
rescue ArgumentError
raise ArgumentError, "invalid value for port: #{port.inspect}"
end
end
end
proxy_uri.user = user if user
proxy_uri.password = pass if pass
@http.proxy = proxy_uri
end
def make_tempfile name
io = Tempfile.new name
io.unlink
io.binmode
io
end
def use_tempfile? size
return false unless @max_file_buffer
return false unless size
size >= @max_file_buffer
end
def reset
@cookie_jar.clear
@history.clear
end
end
require 'mechanize/http/auth_store'
mechanize-2.10.1/lib/mechanize/http/auth_realm.rb 0000644 0000041 0000041 00000001056 14645745627 021752 0 ustar www-data www-data # frozen_string_literal: true
class Mechanize::HTTP::AuthRealm
attr_reader :scheme
attr_reader :uri
attr_reader :realm
def initialize scheme, uri, realm
@scheme = scheme
@uri = uri
@realm = realm if realm
end
def == other
self.class === other and
@scheme == other.scheme and
@uri == other.uri and
@realm == other.realm
end
alias eql? ==
def hash # :nodoc:
[@scheme, @uri, @realm].hash
end
def inspect # :nodoc:
"#" % [@scheme, @uri, @realm]
end
end
mechanize-2.10.1/lib/mechanize/http/content_disposition_parser.rb 0000644 0000041 0000041 00000010632 14645745627 025303 0 ustar www-data www-data # frozen_string_literal: true
# coding: BINARY
require 'strscan'
require 'time'
class Mechanize::HTTP
ContentDisposition = Struct.new :type, :filename, :creation_date,
:modification_date, :read_date, :size, :parameters
end
##
# Parser Content-Disposition headers that loosely follows RFC 2183.
#
# Beyond RFC 2183, this parser allows:
#
# * Missing disposition-type
# * Multiple semicolons
# * Whitespace around semicolons
# * Dates in ISO 8601 format
class Mechanize::HTTP::ContentDispositionParser
attr_accessor :scanner # :nodoc:
@parser = nil
##
# Parses the disposition type and params in the +content_disposition+
# string. The "Content-Disposition:" must be removed.
def self.parse content_disposition
@parser ||= self.new
@parser.parse content_disposition
end
##
# Creates a new parser Content-Disposition headers
def initialize
@scanner = nil
end
##
# Parses the +content_disposition+ header. If +header+ is set to true the
# "Content-Disposition:" portion will be parsed
def parse content_disposition, header = false
return nil if content_disposition.empty?
@scanner = StringScanner.new content_disposition
if header then
return nil unless @scanner.scan(/Content-Disposition/i)
return nil unless @scanner.scan(/:/)
spaces
end
type = rfc_2045_token
@scanner.scan(/;+/)
if @scanner.peek(1) == '=' then
@scanner.pos = 0
type = nil
end
disposition = Mechanize::HTTP::ContentDisposition.new type
spaces
return nil unless parameters = parse_parameters
disposition.filename = parameters.delete 'filename'
disposition.creation_date = parameters.delete 'creation-date'
disposition.modification_date = parameters.delete 'modification-date'
disposition.read_date = parameters.delete 'read-date'
disposition.size = parameters.delete 'size'
disposition.parameters = parameters
disposition
end
##
# Extracts disposition-param and returns a Hash.
def parse_parameters
parameters = {}
while true do
return nil unless param = rfc_2045_token
param.downcase!
return nil unless @scanner.scan(/=/)
value = case param
when /^filename$/ then
rfc_2045_value
when /^(creation|modification|read)-date$/ then
date = rfc_2045_quoted_string
begin
Time.rfc822 date
rescue ArgumentError
begin
Time.iso8601 date
rescue ArgumentError
nil
end
end
when /^size$/ then
rfc_2045_value.to_i(10)
else
rfc_2045_value
end
return nil unless value
parameters[param] = value
spaces
break if @scanner.eos? or not @scanner.scan(/;+/)
spaces
end
parameters
end
##
# quoted-string = <"> *(qtext/quoted-pair) <">
# qtext = , "\" & CR,
# and including linear-white-space
# quoted-pair = "\" CHAR
#
# Parses an RFC 2045 quoted-string
def rfc_2045_quoted_string
return nil unless @scanner.scan(/"/)
text = String.new
while true do
chunk = @scanner.scan(/[\000-\014\016-\041\043-\133\135-\177]+/) # not \r "
if chunk then
text << chunk
if @scanner.peek(1) == '\\' then
@scanner.get_byte
return nil if @scanner.eos?
text << @scanner.get_byte
elsif @scanner.scan(/\r\n[\t ]+/) then
text << " "
end
else
if '\\"' == @scanner.peek(2) then
@scanner.skip(/\\/)
text << @scanner.get_byte
elsif '"' == @scanner.peek(1) then
@scanner.get_byte
break
else
return nil
end
end
end
text
end
##
# token := 1*
#
# Parses an RFC 2045 token
def rfc_2045_token
@scanner.scan(/[^\000-\037\177()<>@,;:\\"\/\[\]?= ]+/)
end
##
# value := token / quoted-string
#
# Parses an RFC 2045 value
def rfc_2045_value
if @scanner.peek(1) == '"' then
rfc_2045_quoted_string
else
rfc_2045_token
end
end
##
# 1*SP
#
# Parses spaces
def spaces
@scanner.scan(/ +/)
end
end
mechanize-2.10.1/lib/mechanize/http/auth_challenge.rb 0000644 0000041 0000041 00000002451 14645745627 022574 0 ustar www-data www-data # frozen_string_literal: true
class Mechanize::HTTP
AuthChallenge = Struct.new :scheme, :params, :raw
##
# A parsed WWW-Authenticate header
class AuthChallenge
##
# :attr_accessor: scheme
#
# The authentication scheme
##
# :attr_accessor: params
#
# The authentication parameters
##
# :method: initialize
#
# :call-seq:
# initialize(scheme = nil, params = nil)
#
# Creates a new AuthChallenge header with the given scheme and parameters
##
# Retrieves +param+ from the params list
def [] param
params[param]
end
##
# Constructs an AuthRealm for this challenge
def realm uri
case scheme
when 'Basic' then
raise ArgumentError, "provide uri for Basic authentication" unless uri
Mechanize::HTTP::AuthRealm.new scheme, uri + '/', self['realm']
when 'Digest' then
Mechanize::HTTP::AuthRealm.new scheme, uri + '/', self['realm']
else
raise Mechanize::Error, "unknown HTTP authentication scheme #{scheme}"
end
end
##
# The name of the realm for this challenge
def realm_name
params['realm'] if Hash === params # NTLM has a string for params
end
##
# The raw authentication challenge
alias to_s raw
end
end
mechanize-2.10.1/lib/mechanize/xml_file.rb 0000644 0000041 0000041 00000002431 14645745627 020447 0 ustar www-data www-data # frozen_string_literal: true
##
# This class encapsulates an XML file. If Mechanize finds a content-type
# of 'text/xml' or 'application/xml' this class will be instantiated and
# returned. This class also opens up the +search+ and +at+ methods available
# on the underlying Nokogiri::XML::Document object.
#
# Example:
#
# require 'mechanize'
#
# agent = Mechanize.new
# xml = agent.get('http://example.org/some-xml-file.xml')
# xml.class #=> Mechanize::XmlFile
# xml.search('//foo[@attr="bar"]/etc')
class Mechanize::XmlFile < Mechanize::File
extend Forwardable
# The underlying Nokogiri::XML::Document object
attr_reader :xml
def initialize(uri = nil, response = nil, body = nil, code = nil)
super uri, response, body, code
@xml = Nokogiri.XML body
end
##
# :method: search
#
# Search for +paths+ in the page using Nokogiri's #search. The +paths+ can
# be XPath or CSS and an optional Hash of namespaces may be appended.
#
# See Nokogiri::XML::Node#search for further details.
def_delegator :xml, :search, :search
##
# :method: at
#
# Search through the page for +path+ under +namespace+ using Nokogiri's #at.
# The +path+ may be either a CSS or XPath expression.
#
# See also Nokogiri::XML::Node#at
def_delegator :xml, :at, :at
end mechanize-2.10.1/lib/mechanize/file_saver.rb 0000644 0000041 0000041 00000001553 14645745627 020773 0 ustar www-data www-data # frozen_string_literal: true
##
# This is a pluggable parser that automatically saves every file it
# encounters. Unlike Mechanize::DirectorySaver, the file saver saves the
# responses as a tree, reflecting the host and file path.
#
# == Example
#
# This example saves all .pdf files
#
# require 'mechanize'
#
# agent = Mechanize.new
# agent.pluggable_parser.pdf = Mechanize::FileSaver
# agent.get 'http://example.com/foo.pdf'
#
# Dir['example.com/*'] # => foo.pdf
class Mechanize::FileSaver < Mechanize::Download
attr_reader :filename
def initialize uri = nil, response = nil, body_io = nil, code = nil
@full_path = true
super
save @filename
end
##
# The save_as alias is provided for backwards compatibility with mechanize
# 2.0. It will be removed in mechanize 3.
#--
# TODO remove in mechanize 3
alias save_as save
end
mechanize-2.10.1/lib/mechanize/parser.rb 0000644 0000041 0000041 00000006604 14645745627 020152 0 ustar www-data www-data # frozen_string_literal: true
##
# The parser module provides standard methods for accessing the headers and
# content of a response that are shared across pluggable parsers.
module Mechanize::Parser
extend Forwardable
special_filenames = Regexp.union %w[
AUX
COM1
COM2
COM3
COM4
COM5
COM6
COM7
COM8
COM9
CON
LPT1
LPT2
LPT3
LPT4
LPT5
LPT6
LPT7
LPT8
LPT9
NUL
PRN
]
##
# Special filenames that must be escaped
SPECIAL_FILENAMES = /\A#{special_filenames}/i
##
# The URI this file was retrieved from
attr_accessor :uri
##
# The Mechanize::Headers for this file
attr_accessor :response
alias header response
##
# The HTTP response code
attr_accessor :code
##
# :method: []
#
# :call-seq:
# [](header)
#
# Access HTTP +header+ by name
def_delegator :header, :[], :[]
##
# :method: []=
#
# :call-seq:
# []=(header, value)
#
# Set HTTP +header+ to +value+
def_delegator :header, :[]=, :[]=
##
# :method: key?
#
# :call-seq:
# key?(header)
#
# Is the named +header+ present?
def_delegator :header, :key?, :key?
##
# :method: each
#
# Enumerate HTTP headers
def_delegator :header, :each, :each
##
# :method: each
#
# Enumerate HTTP headers in capitalized (canonical) form
def_delegator :header, :canonical_each, :canonical_each
##
# Extracts the filename from a Content-Disposition header in the #response
# or from the URI. If +full_path+ is true the filename will include the
# host name and path to the resource, otherwise a filename in the current
# directory is given.
def extract_filename full_path = @full_path
handled = false
if @uri then
uri = @uri
uri += 'index.html' if uri.path.end_with? '/'
path = uri.path.split(/\//)
filename = path.pop || 'index.html'
else
path = []
filename = 'index.html'
end
# Set the filename
if (disposition = @response['content-disposition'])
content_disposition =
Mechanize::HTTP::ContentDispositionParser.parse disposition
if content_disposition && content_disposition.filename && content_disposition.filename != ''
filename = content_disposition.filename
filename = filename.rpartition(/[\\\/]/).last
handled = true
end
end
if not handled and @uri then
filename << '.html' unless filename =~ /\./
filename << "?#{@uri.query}" if @uri.query
end
if SPECIAL_FILENAMES =~ filename then
filename = "_#{filename}"
end
filename = filename.tr "\x00-\x20<>:\"/\\|?*", '_'
@filename = if full_path then
File.join @uri.host, path, filename
else
filename
end
end
##
# Creates a Mechanize::Header from the Net::HTTPResponse +response+.
#
# This allows the Net::HTTPResponse to be garbage collected sooner.
def fill_header response
@response = Mechanize::Headers.new
response.each { |k,v|
@response[k] = v
} if response
@response
end
##
# Finds a free filename based on +filename+, but is not race-free
def find_free_name filename
base_filename = filename ||= @filename
number = 1
while File.exist? filename do
filename = "#{base_filename}.#{number}"
number += 1
end
filename
end
end
mechanize-2.10.1/lib/mechanize/file_response.rb 0000644 0000041 0000041 00000002634 14645745627 021512 0 ustar www-data www-data # frozen_string_literal: true
##
# Fake response for dealing with file:/// requests
class Mechanize::FileResponse
attr_reader :file_path
def initialize(file_path)
@file_path = file_path
@uri = nil
end
def read_body
raise Mechanize::ResponseCodeError.new(self) unless
File.exist? @file_path
if directory?
yield dir_body
else
::File.open(@file_path, 'rb') do |io|
yield io.read
end
end
end
def code
File.exist?(@file_path) ? 200 : 404
end
def content_length
return dir_body.length if directory?
File.exist?(@file_path) ? File.stat(@file_path).size : 0
end
def each_header; end
def [](key)
return nil if key.casecmp('Content-Type') != 0
return 'text/html' if directory?
return 'text/html' if ['.html', '.xhtml'].any? { |extn|
@file_path.end_with?(extn)
}
nil
end
def each
end
def get_fields(key)
[]
end
def http_version
'0'
end
def message
File.exist?(@file_path) ? 'OK' : 'Not Found'
end
def uri
@uri ||= URI "file://#{@file_path}"
end
private
def dir_body
body = %w[]
body.concat Dir[File.join(@file_path, '*')].map { |f|
"#{File.basename(f)}"
}
body << %w[]
body.join("\n").force_encoding(Encoding::BINARY)
end
def directory?
File.directory?(@file_path)
end
end
mechanize-2.10.1/GUIDE.rdoc 0000644 0000041 0000041 00000013266 14645745627 015330 0 ustar www-data www-data = Getting Started With Mechanize
This guide is meant to get you started using Mechanize. By the end of this
guide, you should be able to fetch pages, click links, fill out and submit
forms, scrape data, and many other hopefully useful things. This guide
really just scratches the surface of what is available, but should be enough
information to get you really going!
== Let's Fetch a Page!
First thing is first. Make sure that you've required mechanize and that you
instantiate a new mechanize object:
require 'rubygems'
require 'mechanize'
agent = Mechanize.new
Now we'll use the agent we've created to fetch a page. Let's fetch google
with our mechanize agent:
page = agent.get('http://google.com/')
What just happened? We told mechanize to go pick up google's main page.
Mechanize stored any cookies that were set, and followed any redirects that
google may have sent. The agent gave us back a page that we can use to
scrape data, find links to click, or find forms to fill out.
Next, let's try finding some links to click.
== Finding Links
Mechanize returns a page object whenever you get a page, post, or submit a
form. When a page is fetched, the agent will parse the page and put a list
of links on the page object.
Now that we've fetched google's homepage, let's try listing all of the links:
page.links.each do |link|
puts link.text
end
We can list the links, but Mechanize gives a few shortcuts to help us find a
link to click on. Let's say we wanted to click the link whose text is 'News'.
Normally, we would have to do this:
page = agent.page.links.find { |l| l.text == 'News' }.click
But Mechanize gives us a shortcut. Instead we can say this:
page = agent.page.link_with(:text => 'News').click
That shortcut says "find all links with the name 'News'". You're probably
thinking "there could be multiple links with that text!", and you would be
correct! If you use the plural form, you can access the list.
If you wanted to click on the second news link, you could do this:
agent.page.links_with(:text => 'News')[1].click
We can even find a link with a certain href like so:
page.link_with(:href => '/something')
Or chain them together to find a link with certain text and certain href:
page.link_with(:text => 'News', :href => '/something')
These shortcuts that Mechanize provides are available on any list that you
can fetch like frames, iframes, or forms. Now that we know how to find and
click links, let's try something more complicated like filling out a form.
== Filling Out Forms
Let's continue with our google example. Here's the code we have so far:
require 'rubygems'
require 'mechanize'
agent = Mechanize.new
page = agent.get('http://google.com/')
If we pretty print the page, we can see that there is one form named 'f',
that has a couple buttons and a few fields:
pp page
Now that we know the name of the form, let's fetch it off the page:
google_form = page.form('f')
Mechanize lets you access form input fields in a few different ways, but the
most convenient is that you can access input fields as accessors on the
object. So let's set the form field named 'q' on the form to 'ruby mechanize':
google_form.q = 'ruby mechanize'
To make sure that we set the value, let's pretty print the form, and you should
see a line similar to this:
#
If you saw that the value of 'q' changed, you're on the right track! Now we
can submit the form and 'press' the submit button and print the results:
page = agent.submit(google_form, google_form.buttons.first)
pp page
What we just did was equivalent to putting text in the search field and
clicking the 'Google Search' button. If we had submitted the form without
a button, it would be like typing in the text field and hitting the return
button.
Let's take a look at the code all together:
require 'rubygems'
require 'mechanize'
agent = Mechanize.new
page = agent.get('http://google.com/')
google_form = page.form('f')
google_form.q = 'ruby mechanize'
page = agent.submit(google_form)
pp page
Before we go on to screen scraping, let's take a look at forms a little more
in depth. Unless you want to skip ahead!
== Advanced Form Techniques
In this section, I want to touch on using the different types in input fields
possible with a form. Password and textarea fields can be treated just like
text input fields. Select fields are very similar to text fields, but they
have many options associated with them. If you select one option, mechanize
will de-select the other options (unless it is a multi select!).
For example, let's select an option on a list:
form.field_with(:name => 'list').options[0].select
Now let's take a look at checkboxes and radio buttons. To select a checkbox,
just check it like this:
form.checkbox_with(:name => 'box').check
Radio buttons are very similar to checkboxes, but they know how to uncheck
other radio buttons of the same name. Just check a radio button like you
would a checkbox:
form.radiobuttons_with(:name => 'box')[1].check
Mechanize also makes file uploads easy! Just find the file upload field, and
tell it what file name you want to upload:
form.file_uploads.first.file_name = "somefile.jpg"
== Scraping Data
Mechanize uses nokogiri[http://nokogiri.org/] to parse HTML. What does this
mean for you? You can treat a mechanize page like an nokogiri object. After
you have used Mechanize to navigate to the page that you need to scrape, then
scrape it using nokogiri methods:
agent.get('http://someurl.com/').search("p.posted")
The expression given to Mechanize::Page#search may be a CSS expression or an
XPath expression:
agent.get('http://someurl.com/').search(".//p[@class='posted']")
mechanize-2.10.1/LICENSE.txt 0000644 0000041 0000041 00000002257 14645745627 015443 0 ustar www-data www-data (The MIT License)
Copyright (c) 2005 by Michael Neumann (mneumann@ntecs.de)
Copyright (c) 2006-2021 by Eric Hodel, Akinori MUSHA, Aaron Patterson, Lee Jarvis, Mike Dalessio
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
mechanize-2.10.1/test/ 0000755 0000041 0000041 00000000000 14645745627 014571 5 ustar www-data www-data mechanize-2.10.1/test/test_mechanize_image.rb 0000644 0000041 0000041 00000000220 14645745627 021254 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeImage < Mechanize::TestCase
# empty subclass, no tests
end
mechanize-2.10.1/test/test_mechanize_redirect_not_get_or_head_error.rb 0000644 0000041 0000041 00000000426 14645745627 026414 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeRedirectNotGetOrHead < Mechanize::TestCase
def test_to_s
page = fake_page
error = Mechanize::RedirectNotGetOrHeadError.new(page, :put)
assert_match(/ PUT /, error.to_s)
end
end
mechanize-2.10.1/test/data/ 0000755 0000041 0000041 00000000000 14645745627 015502 5 ustar www-data www-data mechanize-2.10.1/test/data/server.csr 0000644 0000041 0000041 00000001304 14645745627 017517 0 ustar www-data www-data -----BEGIN CERTIFICATE REQUEST-----
MIIB0jCCATsCAQAwgZExCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
MRAwDgYDVQQHEwdTZWF0dGxlMRIwEAYDVQQKEwlNZWNoYW5pemUxEjAQBgNVBAsT
CU1lY2hhbml6ZTEOMAwGA1UEAxMFQWFyb24xIzAhBgkqhkiG9w0BCQEWFGFhcm9u
cEBydWJ5Zm9yZ2Uub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqZ5iO
GOLQc90ibB0dvEKFK+yGMZKmw/Ko6oCDdC1zrJchcohszSnGuS59gLvAmS8groLf
77rY31fhKtG5dB8GynaOh4z+kcZhl+hCll+zTq7KH/rPeg4S5iWllm7b6j/HssvT
zSJyo1+p/+8LFXrULrY4Tcv3AJK4elDrI8ghrwIDAQABoAAwDQYJKoZIhvcNAQEE
BQADgYEAT7SPe71NQvT2BYGEmbWb7FlSQrPh+rDQMHt/Akb8+r91NLkxZtbD1e/F
iyI9JloPCEwJXxHBl0VVRpFCRuJNN0z0E/G4NUWu6n+ZkihtnmV6uazzAQmD4pTl
SjoiyVLWU+r4Q4yXWXtJ9GR8Attv32fL3PcP+GGLeurXJAn0MNU=
-----END CERTIFICATE REQUEST-----
mechanize-2.10.1/test/data/htpasswd 0000644 0000041 0000041 00000000023 14645745627 017255 0 ustar www-data www-data mech:44E/qORekFV0E
mechanize-2.10.1/test/data/server.pem 0000644 0000041 0000041 00000001567 14645745627 017524 0 ustar www-data www-data -----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCqZ5iOGOLQc90ibB0dvEKFK+yGMZKmw/Ko6oCDdC1zrJchcohs
zSnGuS59gLvAmS8groLf77rY31fhKtG5dB8GynaOh4z+kcZhl+hCll+zTq7KH/rP
eg4S5iWllm7b6j/HssvTzSJyo1+p/+8LFXrULrY4Tcv3AJK4elDrI8ghrwIDAQAB
AoGAC+iZfLS4hSDTv2gW0NErROtA6E/mk8j12GArAwTHeGIDXc8HQbNEzCJ84UBx
3o/V/06yzruOL0HMfmvjpDY9RLsH02xZb2F/lruw4MJLu50i/Zu8Sjmb1YPSfCh/
3+8lREA3Uznlq+wHC3yPxQzMBy5jaEdH4IKxT0Bq8TeF0AECQQDSpL47YpRVRsLn
sS00ndEgQQmT5AJWJJtPpbHk6AA0a+zdNeuDRbdF42zG483YEqU7meZbPKR8QbkK
ZQPEBuevAkEAzxjGcz6NZesmN/NQOtOpylewEs1bdIJyBIBmcnmkimLBtdxd0t34
wUKVHLDSj2aemuAHHwsyn/BNXs6F+obmAQJBALpbkAXAAFW1xefvo3vih8sOXyfd
WIfX2SRNBqbq7otyVFudQaChBDUrsOgBUPLyBAdH8DoV27wm9UuR9RPvu/cCQFRr
WgICXqtMFtE56tuACreD1S9k7MHqpsW0/Y3ujicnKKWUhd5+Q3esR5JhdgOkpkSl
y+FYtDNERpW+BBliwgECQA+Vc7pnxwDIOP8kFumdAUmRmhEZjuwArFcywPzrCUn9
4/KBOp5wDN7kanBwNGZCZ/eQtkb6thAS8C9pufHD1lw=
-----END RSA PRIVATE KEY-----
mechanize-2.10.1/test/data/server.key 0000644 0000041 0000041 00000001567 14645745627 017533 0 ustar www-data www-data -----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCqZ5iOGOLQc90ibB0dvEKFK+yGMZKmw/Ko6oCDdC1zrJchcohs
zSnGuS59gLvAmS8groLf77rY31fhKtG5dB8GynaOh4z+kcZhl+hCll+zTq7KH/rP
eg4S5iWllm7b6j/HssvTzSJyo1+p/+8LFXrULrY4Tcv3AJK4elDrI8ghrwIDAQAB
AoGAC+iZfLS4hSDTv2gW0NErROtA6E/mk8j12GArAwTHeGIDXc8HQbNEzCJ84UBx
3o/V/06yzruOL0HMfmvjpDY9RLsH02xZb2F/lruw4MJLu50i/Zu8Sjmb1YPSfCh/
3+8lREA3Uznlq+wHC3yPxQzMBy5jaEdH4IKxT0Bq8TeF0AECQQDSpL47YpRVRsLn
sS00ndEgQQmT5AJWJJtPpbHk6AA0a+zdNeuDRbdF42zG483YEqU7meZbPKR8QbkK
ZQPEBuevAkEAzxjGcz6NZesmN/NQOtOpylewEs1bdIJyBIBmcnmkimLBtdxd0t34
wUKVHLDSj2aemuAHHwsyn/BNXs6F+obmAQJBALpbkAXAAFW1xefvo3vih8sOXyfd
WIfX2SRNBqbq7otyVFudQaChBDUrsOgBUPLyBAdH8DoV27wm9UuR9RPvu/cCQFRr
WgICXqtMFtE56tuACreD1S9k7MHqpsW0/Y3ujicnKKWUhd5+Q3esR5JhdgOkpkSl
y+FYtDNERpW+BBliwgECQA+Vc7pnxwDIOP8kFumdAUmRmhEZjuwArFcywPzrCUn9
4/KBOp5wDN7kanBwNGZCZ/eQtkb6thAS8C9pufHD1lw=
-----END RSA PRIVATE KEY-----
mechanize-2.10.1/test/data/server.crt 0000644 0000041 0000041 00000001704 14645745627 017524 0 ustar www-data www-data -----BEGIN CERTIFICATE-----
MIICmzCCAgQCCQDq2kM3TCIM0DANBgkqhkiG9w0BAQQFADCBkTELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxEjAQBgNV
BAoTCU1lY2hhbml6ZTESMBAGA1UECxMJTWVjaGFuaXplMQ4wDAYDVQQDEwVBYXJv
bjEjMCEGCSqGSIb3DQEJARYUYWFyb25wQHJ1Ynlmb3JnZS5vcmcwHhcNMDYwODIz
MDU0NTMwWhcNMDcwODIzMDU0NTMwWjCBkTELMAkGA1UEBhMCVVMxEzARBgNVBAgT
Cldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxEjAQBgNVBAoTCU1lY2hhbml6
ZTESMBAGA1UECxMJTWVjaGFuaXplMQ4wDAYDVQQDEwVBYXJvbjEjMCEGCSqGSIb3
DQEJARYUYWFyb25wQHJ1Ynlmb3JnZS5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0A
MIGJAoGBAKpnmI4Y4tBz3SJsHR28QoUr7IYxkqbD8qjqgIN0LXOslyFyiGzNKca5
Ln2Au8CZLyCugt/vutjfV+Eq0bl0HwbKdo6HjP6RxmGX6EKWX7NOrsof+s96DhLm
JaWWbtvqP8eyy9PNInKjX6n/7wsVetQutjhNy/cAkrh6UOsjyCGvAgMBAAEwDQYJ
KoZIhvcNAQEEBQADgYEAGtqgxn1fh0X5MxDG1yMp5aGcZ6HhtEtlm5S0ZsRnMsqU
Hh6Bd57+zUQ66XnLCbQN2cwNeeSoqtI16Ccc1I5cAhQnIZESMsPG21i1BnpEhKph
HfNFNpWI/upT2EXNUM6Vx2Kk2aCw2ysrD2pHpsTo5bCOly00uK1ZkoJVQMTL4gU=
-----END CERTIFICATE-----
mechanize-2.10.1/test/test_mechanize_file_saver.rb 0000644 0000041 0000041 00000000561 14645745627 022321 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeFileSaver < Mechanize::TestCase
def setup
super
@uri = URI 'http://example'
@io = StringIO.new 'hello world'
end
def test_initialize
in_tmpdir do
Mechanize::FileSaver.new @uri, nil, @io, 200
assert File.exist? 'example/index.html'
end
end
end
mechanize-2.10.1/test/test_mechanize_subclass.rb 0000644 0000041 0000041 00000000605 14645745627 022020 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeSubclass < Mechanize::TestCase
class Parent < Mechanize
@html_parser = :parser
@log = :log
end
class Child < Parent
end
def test_subclass_inherits_html_parser
assert_equal :parser, Child.html_parser
end
def test_subclass_inherits_log
assert_equal :log, Child.log
end
end
mechanize-2.10.1/test/test_mechanize_cookie.rb 0000644 0000041 0000041 00000040722 14645745627 021456 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
module Enumerable
def combine
masks = inject([[], 1]){|(ar, m), e| [ar << m, m << 1 ] }[0]
all = masks.inject(0){ |al, m| al|m }
result = []
for i in 1..all do
tmp = []
each_with_index do |e, idx|
tmp << e unless (masks[idx] & i) == 0
end
result << tmp
end
result
end
end
class TestMechanizeCookie < Mechanize::TestCase
def assert_cookie_parse url, cookie_text, &block
cookie = nil
block ||= proc { |p_cookie| cookie = p_cookie }
exp_re = /The call of Mechanize::Cookie.parse/
assert_output "", exp_re do
Mechanize::Cookie.parse(url, cookie_text, &block)
end
cookie
end
alias silently capture_io
def test_parse_dates
url = URI.parse('http://localhost/')
yesterday = Time.now - 86400
dates = [ "14 Apr 89 03:20:12",
"14 Apr 89 03:20 GMT",
"Fri, 17 Mar 89 4:01:33",
"Fri, 17 Mar 89 4:01 GMT",
"Mon Jan 16 16:12 PDT 1989",
#"Mon Jan 16 16:12 +0130 1989",
"6 May 1992 16:41-JST (Wednesday)",
#"22-AUG-1993 10:59:12.82",
"22-AUG-1993 10:59pm",
"22-AUG-1993 12:59am",
"22-AUG-1993 12:59 PM",
#"Friday, August 04, 1995 3:54 PM",
#"06/21/95 04:24:34 PM",
#"20/06/95 21:07",
#"95-06-08 19:32:48 EDT",
]
dates.each do |date|
cookie = "PREF=1; expires=#{date}"
silently do
Mechanize::Cookie.parse(url, cookie) { |c|
assert c.expires, "Tried parsing: #{date}"
assert_equal(true, c.expires < yesterday)
}
end
end
end
def test_parse_empty
cookie_str = 'a=b; ; c=d'
uri = URI.parse 'http://example'
assert_cookie_parse uri, cookie_str do |cookie|
assert_equal 'a', cookie.name
assert_equal 'b', cookie.value
end
end
def test_parse_no_space
cookie_str = "foo=bar;Expires=Sun, 06 Nov 2011 00:28:06 GMT;Path=/"
uri = URI.parse 'http://example'
assert_cookie_parse uri, cookie_str do |cookie|
assert_equal 'foo', cookie.name
assert_equal 'bar', cookie.value
assert_equal '/', cookie.path
assert_equal Time.at(1320539286), cookie.expires
end
end
def test_parse_quoted
cookie_str =
"quoted=\"value\"; Expires=Sun, 06 Nov 2011 00:11:18 GMT; Path=/"
uri = URI.parse 'http://example'
assert_cookie_parse uri, cookie_str do |cookie|
assert_equal 'quoted', cookie.name
assert_equal 'value', cookie.value
end
end
def test_parse_weird_cookie
cookie = 'n/a, ASPSESSIONIDCSRRQDQR=FBLDGHPBNDJCPCGNCPAENELB; path=/'
url = URI.parse('http://www.searchinnovation.com/')
assert_cookie_parse url, cookie do |c|
assert_equal('ASPSESSIONIDCSRRQDQR', c.name)
assert_equal('FBLDGHPBNDJCPCGNCPAENELB', c.value)
end
end
def test_double_semicolon
double_semi = 'WSIDC=WEST;; domain=.williams-sonoma.com; path=/'
url = URI.parse('http://williams-sonoma.com/')
assert_cookie_parse url, double_semi do |cookie|
assert_equal('WSIDC', cookie.name)
assert_equal('WEST', cookie.value)
end
end
def test_parse_bad_version
bad_cookie = 'PRETANET=TGIAqbFXtt; Name=/PRETANET; Path=/; Version=1.2; Content-type=text/html; Domain=192.168.6.196; expires=Friday, 13-November-2026 23:01:46 GMT;'
url = URI.parse('http://localhost/')
assert_cookie_parse url, bad_cookie do |cookie|
assert_nil(cookie.version)
end
end
def test_parse_bad_max_age
bad_cookie = 'PRETANET=TGIAqbFXtt; Name=/PRETANET; Path=/; Max-Age=1.2; Content-type=text/html; Domain=192.168.6.196; expires=Friday, 13-November-2026 23:01:46 GMT;'
url = URI.parse('http://localhost/')
assert_cookie_parse url, bad_cookie do |cookie|
assert_nil(cookie.max_age)
end
end
def test_parse_date_fail
url = URI.parse('http://localhost/')
dates = [
"20/06/95 21:07",
]
silently do
dates.each do |date|
cookie = "PREF=1; expires=#{date}"
Mechanize::Cookie.parse(url, cookie) { |c|
assert_equal(true, c.expires.nil?)
}
end
end
end
def test_parse_domain_dot
url = URI.parse('http://host.example.com/')
cookie_str = 'a=b; domain=.example.com'
cookie = assert_cookie_parse url, cookie_str
assert_equal 'example.com', cookie.domain
assert cookie.for_domain?
end
def test_parse_domain_no_dot
url = URI.parse('http://host.example.com/')
cookie_str = 'a=b; domain=example.com'
cookie = assert_cookie_parse url, cookie_str
assert_equal 'example.com', cookie.domain
assert cookie.for_domain?
end
def test_parse_domain_none
url = URI.parse('http://example.com/')
cookie_str = 'a=b;'
cookie = assert_cookie_parse url, cookie_str
assert_equal 'example.com', cookie.domain
assert !cookie.for_domain?
end
def test_parse_max_age
url = URI.parse('http://localhost/')
date = 'Mon, 19 Feb 2012 19:26:04 GMT'
cookie_text = "name=Akinori; expires=#{date}"
cookie = assert_cookie_parse url, cookie_text
assert_equal Time.at(1329679564), cookie.expires
cookie_text = 'name=Akinori; max-age=3600'
cookie = assert_cookie_parse url, cookie_text
assert_in_delta Time.now + 3600, cookie.expires, 1
# Max-Age has precedence over Expires
cookie_text = "name=Akinori; max-age=3600; expires=#{date}"
cookie = assert_cookie_parse url, cookie_text
assert_in_delta Time.now + 3600, cookie.expires, 1
cookie_text = "name=Akinori; expires=#{date}; max-age=3600"
cookie = assert_cookie_parse url, cookie_text
assert_in_delta Time.now + 3600, cookie.expires, 1
end
def test_parse_expires_session
url = URI.parse('http://localhost/')
[
'name=Akinori',
'name=Akinori; expires',
'name=Akinori; max-age',
'name=Akinori; expires=',
'name=Akinori; max-age=',
].each { |str|
cookie = assert_cookie_parse url, str
assert cookie.session, str
}
[
'name=Akinori; expires=Mon, 19 Feb 2012 19:26:04 GMT',
'name=Akinori; max-age=3600',
].each { |str|
cookie = assert_cookie_parse url, str
assert !cookie.session, str
}
end
def test_parse_many
url = URI 'http://localhost/'
cookie_str =
"name=Aaron; Domain=localhost; Expires=Sun, 06 Nov 2011 00:29:51 GMT; Path=/, " \
"name=Aaron; Domain=localhost; Expires=Sun, 06 Nov 2011 00:29:51 GMT; Path=/, " \
"name=Aaron; Domain=localhost; Expires=Sun, 06 Nov 2011 00:29:51 GMT; Path=/, " \
"name=Aaron; Domain=localhost; Expires=Sun, 06 Nov 2011 00:29:51 GMT; Path=/; HttpOnly, " \
"expired=doh; Expires=Fri, 04 Nov 2011 00:29:51 GMT; Path=/, " \
"a_path=some_path; Expires=Sun, 06 Nov 2011 00:29:51 GMT; Path=/some_path, " \
"no_path1=no_path; Expires=Sun, 06 Nov 2011 00:29:52 GMT, no_expires=nope; Path=/, " \
"no_path2=no_path; Expires=Sun, 06 Nov 2011 00:29:52 GMT; no_expires=nope; Path, " \
"no_path3=no_path; Expires=Sun, 06 Nov 2011 00:29:52 GMT; no_expires=nope; Path=, " \
"no_domain1=no_domain; Expires=Sun, 06 Nov 2011 00:29:53 GMT; no_expires=nope, " \
"no_domain2=no_domain; Expires=Sun, 06 Nov 2011 00:29:53 GMT; no_expires=nope; Domain, " \
"no_domain3=no_domain; Expires=Sun, 06 Nov 2011 00:29:53 GMT; no_expires=nope; Domain="
cookies = nil
silently { cookies = Mechanize::Cookie.parse url, cookie_str }
assert_equal 13, cookies.length
name = cookies.find { |c| c.name == 'name' }
assert_equal "Aaron", name.value
assert_equal "/", name.path
assert_equal Time.at(1320539391), name.expires
a_path = cookies.find { |c| c.name == 'a_path' }
assert_equal "some_path", a_path.value
assert_equal "/some_path", a_path.path
assert_equal Time.at(1320539391), a_path.expires
no_expires = cookies.find { |c| c.name == 'no_expires' }
assert_equal "nope", no_expires.value
assert_equal "/", no_expires.path
assert_nil no_expires.expires
no_path_cookies = cookies.select { |c| c.value == 'no_path' }
assert_equal 3, no_path_cookies.size
no_path_cookies.each { |c|
assert_equal "/", c.path, c.name
assert_equal Time.at(1320539392), c.expires, c.name
}
no_domain_cookies = cookies.select { |c| c.value == 'no_domain' }
assert_equal 3, no_domain_cookies.size
no_domain_cookies.each { |c|
assert !c.for_domain?, c.name
assert_equal c.domain, url.host, c.name
assert_equal Time.at(1320539393), c.expires, c.name
}
assert cookies.find { |c| c.name == 'expired' }
end
def test_parse_valid_cookie
url = URI.parse('http://rubygems.org/')
cookie_params = {}
cookie_params['expires'] = 'expires=Sun, 27-Sep-2037 00:00:00 GMT'
cookie_params['path'] = 'path=/'
cookie_params['domain'] = 'domain=.rubygems.org'
cookie_params['httponly'] = 'HttpOnly'
cookie_value = '12345%7D=ASDFWEE345%3DASda'
expires = Time.parse('Sun, 27-Sep-2037 00:00:00 GMT')
cookie_params.keys.combine.each do |c|
cookie_text = +"#{cookie_value}; "
c.each_with_index do |key, idx|
if idx == (c.length - 1)
cookie_text << "#{cookie_params[key]}"
else
cookie_text << "#{cookie_params[key]}; "
end
end
cookie = assert_cookie_parse url, cookie_text
assert_equal('12345%7D=ASDFWEE345%3DASda', cookie.to_s)
assert_equal('/', cookie.path)
# if expires was set, make sure we parsed it
if c.find { |k| k == 'expires' }
assert_equal(expires, cookie.expires)
else
assert_nil(cookie.expires)
end
end
end
def test_parse_valid_cookie_empty_value
url = URI.parse('http://rubygems.org/')
cookie_params = {}
cookie_params['expires'] = 'expires=Sun, 27-Sep-2037 00:00:00 GMT'
cookie_params['path'] = 'path=/'
cookie_params['domain'] = 'domain=.rubygems.org'
cookie_params['httponly'] = 'HttpOnly'
cookie_value = '12345%7D='
expires = Time.parse('Sun, 27-Sep-2037 00:00:00 GMT')
cookie_params.keys.combine.each do |c|
cookie_text = +"#{cookie_value}; "
c.each_with_index do |key, idx|
if idx == (c.length - 1)
cookie_text << "#{cookie_params[key]}"
else
cookie_text << "#{cookie_params[key]}; "
end
end
cookie = assert_cookie_parse url, cookie_text
assert_equal('12345%7D=', cookie.to_s)
assert_equal('', cookie.value)
assert_equal('/', cookie.path)
# if expires was set, make sure we parsed it
if c.find { |k| k == 'expires' }
assert_equal(expires, cookie.expires)
else
assert_nil(cookie.expires)
end
end
end
# If no path was given, use the one from the URL
def test_cookie_using_url_path
url = URI.parse('http://rubygems.org/login.php')
cookie_params = {}
cookie_params['expires'] = 'expires=Sun, 27-Sep-2037 00:00:00 GMT'
cookie_params['path'] = 'path=/'
cookie_params['domain'] = 'domain=.rubygems.org'
cookie_params['httponly'] = 'HttpOnly'
cookie_value = '12345%7D=ASDFWEE345%3DASda'
expires = Time.parse('Sun, 27-Sep-2037 00:00:00 GMT')
cookie_params.keys.combine.each do |c|
next if c.find { |k| k == 'path' }
cookie_text = +"#{cookie_value}; "
c.each_with_index do |key, idx|
if idx == (c.length - 1)
cookie_text << "#{cookie_params[key]}"
else
cookie_text << "#{cookie_params[key]}; "
end
end
cookie = assert_cookie_parse url, cookie_text
assert_equal('12345%7D=ASDFWEE345%3DASda', cookie.to_s)
assert_equal('/', cookie.path)
# if expires was set, make sure we parsed it
if c.find { |k| k == 'expires' }
assert_equal(expires, cookie.expires)
else
assert_nil(cookie.expires)
end
end
end
# Test using secure cookies
def test_cookie_with_secure
url = URI.parse('http://rubygems.org/')
cookie_params = {}
cookie_params['expires'] = 'expires=Sun, 27-Sep-2037 00:00:00 GMT'
cookie_params['path'] = 'path=/'
cookie_params['domain'] = 'domain=.rubygems.org'
cookie_params['secure'] = 'secure'
cookie_value = '12345%7D=ASDFWEE345%3DASda'
expires = Time.parse('Sun, 27-Sep-2037 00:00:00 GMT')
cookie_params.keys.combine.each do |c|
next unless c.find { |k| k == 'secure' }
cookie_text = +"#{cookie_value}; "
c.each_with_index do |key, idx|
if idx == (c.length - 1)
cookie_text << "#{cookie_params[key]}"
else
cookie_text << "#{cookie_params[key]}; "
end
end
cookie = assert_cookie_parse url, cookie_text
assert_equal('12345%7D=ASDFWEE345%3DASda', cookie.to_s)
assert_equal('/', cookie.path)
assert_equal(true, cookie.secure)
# if expires was set, make sure we parsed it
if c.find { |k| k == 'expires' }
assert_equal(expires, cookie.expires)
else
assert_nil(cookie.expires)
end
end
end
def test_parse_cookie_no_spaces
url = URI.parse('http://rubygems.org/')
cookie_params = {}
cookie_params['expires'] = 'expires=Sun, 27-Sep-2037 00:00:00 GMT'
cookie_params['path'] = 'path=/'
cookie_params['domain'] = 'domain=.rubygems.org'
cookie_params['httponly'] = 'HttpOnly'
cookie_value = '12345%7D=ASDFWEE345%3DASda'
expires = Time.parse('Sun, 27-Sep-2037 00:00:00 GMT')
cookie_params.keys.combine.each do |c|
cookie_text = +"#{cookie_value};"
c.each_with_index do |key, idx|
if idx == (c.length - 1)
cookie_text << "#{cookie_params[key]}"
else
cookie_text << "#{cookie_params[key]};"
end
end
cookie = assert_cookie_parse url, cookie_text
assert_equal('12345%7D=ASDFWEE345%3DASda', cookie.to_s)
assert_equal('/', cookie.path)
# if expires was set, make sure we parsed it
if c.find { |k| k == 'expires' }
assert_equal(expires, cookie.expires)
else
assert_nil(cookie.expires)
end
end
end
def test_new
cookie = Mechanize::Cookie.new('key', 'value')
assert_equal 'key', cookie.name
assert_equal 'value', cookie.value
assert_nil cookie.expires
# Minimum unit for the expires attribute is second
expires = Time.at((Time.now + 3600).to_i)
cookie = Mechanize::Cookie.new('key', 'value', :expires => expires.dup)
assert_equal 'key', cookie.name
assert_equal 'value', cookie.value
assert_equal expires, cookie.expires
cookie = Mechanize::Cookie.new(:value => 'value', :name => 'key', :expires => expires.dup)
assert_equal 'key', cookie.name
assert_equal 'value', cookie.value
assert_equal expires, cookie.expires
end
def test_domain=
url = URI.parse('http://host.dom.example.com:8080/')
cookie_str = 'a=b; domain=Example.Com'
cookie = assert_cookie_parse url, cookie_str
assert 'example.com', cookie.domain
cookie.domain = DomainName(url.host)
assert 'host.dom.example.com', cookie.domain
cookie.domain = 'Dom.example.com'
assert 'dom.example.com', cookie.domain
new_domain = Object.new.tap { |o|
def o.to_str
'Example.com'
end
}
cookie.domain = new_domain
assert 'example.com', cookie.domain
new_domain = Object.new.tap { |o|
def o.to_str
'Example2.com'
end
}
assert_output nil, /The call of Mechanize::Cookie#set_domain/ do
cookie.set_domain(new_domain)
end
assert 'example2.com', cookie.domain
end
def test_cookie_httponly
url = URI.parse('http://rubygems.org/')
cookie_params = {}
cookie_params['httponly'] = 'HttpOnly'
cookie_value = '12345%7D=ASDFWEE345%3DASda'
expires = Time.parse('Sun, 27-Sep-2037 00:00:00 GMT')
cookie_params.keys.combine.each do |c|
cookie_text = +"#{cookie_value}; "
c.each_with_index do |key, idx|
if idx == (c.length - 1)
cookie_text << "#{cookie_params[key]}"
else
cookie_text << "#{cookie_params[key]}; "
end
end
cookie = assert_cookie_parse url, cookie_text
assert_equal(true, cookie.httponly)
# if expires was set, make sure we parsed it
if c.find { |k| k == 'expires' }
assert_equal(expires, cookie.expires)
else
assert_nil(cookie.expires)
end
end
end
end
mechanize-2.10.1/test/test_mechanize_http_content_disposition_parser.rb 0000644 0000041 0000041 00000010741 14645745627 026714 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeHttpContentDispositionParser < Mechanize::TestCase
def setup
super
@parser = Mechanize::HTTP::ContentDispositionParser.new
end
def test_parse
now = Time.at Time.now.to_i
content_disposition = @parser.parse \
'attachment;' \
'filename=value;' \
"creation-date=\"#{now.rfc822}\";" \
"modification-date=\"#{(now + 1).rfc822}\";" \
"read-date=\"#{(now + 2).rfc822}\";" \
'size=5;' \
'arbitrary=value'
assert_equal 'attachment', content_disposition.type
assert_equal 'value', content_disposition.filename
assert_equal now, content_disposition.creation_date
assert_equal((now + 1), content_disposition.modification_date)
assert_equal((now + 2), content_disposition.read_date)
assert_equal 5, content_disposition.size
expected = { 'arbitrary' => 'value' }
assert_equal expected, content_disposition.parameters
end
def test_parse_date_iso8601_fallback
now = Time.at Time.now.to_i
content_disposition = @parser.parse \
'attachment;' \
'filename=value;' \
"creation-date=\"#{now.iso8601}\";" \
"modification-date=\"#{(now + 1).iso8601}\""
assert_equal 'attachment', content_disposition.type
assert_equal 'value', content_disposition.filename
assert_equal now, content_disposition.creation_date
assert_equal((now + 1), content_disposition.modification_date)
end
def test_parse_date_invalid
now = Time.at Time.now.to_i
content_disposition = @parser.parse \
'attachment;' \
'filename=value;' \
"creation-date=\"#{now.to_s}\";" \
"modification-date=\"#{(now + 1).to_s}\""
assert_nil content_disposition
end
def test_parse_header
content_disposition = @parser.parse \
'content-disposition: attachment;filename=value', true
assert_equal 'attachment', content_disposition.type
assert_equal 'value', content_disposition.filename
end
def test_parse_no_type
content_disposition = @parser.parse 'filename=value'
assert_nil content_disposition.type
assert_equal 'value', content_disposition.filename
end
def test_parse_semicolons
content_disposition = @parser.parse 'attachment;;filename=value'
assert_equal 'attachment', content_disposition.type
assert_equal 'value', content_disposition.filename
end
def test_parse_quoted_size
content_disposition = @parser.parse 'size="5"'
assert_equal 5, content_disposition.size
end
def test_rfc_2045_quoted_string
@parser.scanner = StringScanner.new '"text"'
string = @parser.rfc_2045_quoted_string
assert_equal 'text', string
end
def test_rfc_2045_quoted_string_bad
@parser.scanner = StringScanner.new '"text'
assert_nil @parser.rfc_2045_quoted_string
end
def test_rfc_2045_quoted_string_crlf
@parser.scanner = StringScanner.new "\"multiline\\\r\n\ttext\""
string = @parser.rfc_2045_quoted_string
assert_equal "multiline\r\n\ttext", string
end
def test_rfc_2045_quoted_string_escape
@parser.scanner = StringScanner.new "\"escape\\ text\""
string = @parser.rfc_2045_quoted_string
assert_equal 'escape text', string
end
def test_rfc_2045_quoted_string_escape_bad
@parser.scanner = StringScanner.new '"escape\\'
string = @parser.rfc_2045_quoted_string
assert_nil string
end
def test_rfc_2045_quoted_string_folded
@parser.scanner = StringScanner.new "\"multiline\r\n\ttext\""
string = @parser.rfc_2045_quoted_string
assert_equal 'multiline text', string
end
def test_rfc_2045_quoted_string_quote
@parser.scanner = StringScanner.new '"escaped \\" here"'
string = @parser.rfc_2045_quoted_string
assert_equal 'escaped " here', string
end
def test_rfc_2045_quoted_string_quote_end
@parser.scanner = StringScanner.new '"end \\""'
string = @parser.rfc_2045_quoted_string
assert_equal 'end "', string
end
def test_parse_uppercase
content_disposition = @parser.parse \
'content-disposition: attachment; Filename=value', true
assert_equal 'attachment', content_disposition.type
assert_equal 'value', content_disposition.filename
end
def test_parse_filename_starting_with_escaped_quote
content_disposition = @parser.parse \
'content-disposition: attachment; Filename="\"value\""', true
assert_equal '"value"', content_disposition.filename
end
end
mechanize-2.10.1/test/test_mechanize_http_www_authenticate_parser.rb 0000644 0000041 0000041 00000011154 14645745627 026177 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeHttpWwwAuthenticateParser < Mechanize::TestCase
def setup
super
@parser = Mechanize::HTTP::WWWAuthenticateParser.new
end
def test_auth_param
@parser.scanner = StringScanner.new 'realm=here'
param = @parser.auth_param
assert_equal %w[realm here], param
end
def test_auth_param_bad_no_value
@parser.scanner = StringScanner.new 'realm='
assert_nil @parser.auth_param
end
def test_auth_param_bad_token
@parser.scanner = StringScanner.new 'realm'
assert_nil @parser.auth_param
end
def test_auth_param_bad_value
@parser.scanner = StringScanner.new 'realm="this '
assert_nil @parser.auth_param
end
def test_auth_param_quoted
@parser.scanner = StringScanner.new 'realm="this site"'
param = @parser.auth_param
assert_equal ['realm', 'this site'], param
end
def test_parse
expected = [
challenge('Basic', { 'realm' => 'foo', 'qop' => 'auth,auth-int' }, 'Basic realm=foo, qop="auth,auth-int"'),
]
assert_equal expected, @parser.parse('Basic realm=foo, qop="auth,auth-int"')
end
def test_parse_without_comma_delimiter
expected = [
challenge('Basic', { 'realm' => 'foo', 'qop' => 'auth,auth-int' }, 'Basic realm=foo qop="auth,auth-int"'),
]
assert_equal expected, @parser.parse('Basic realm=foo qop="auth,auth-int"')
end
def test_parse_multiple
expected = [
challenge('Basic', { 'realm' => 'foo' }, 'Basic realm=foo'),
challenge('Digest', { 'realm' => 'bar' }, 'Digest realm=bar'),
]
assert_equal expected, @parser.parse('Basic realm=foo, Digest realm=bar')
end
def test_parse_multiple_without_comma_delimiter
expected = [
challenge('Basic', { 'realm' => 'foo' }, 'Basic realm=foo'),
challenge('Digest', { 'realm' => 'bar' }, 'Digest realm=bar'),
]
assert_equal expected, @parser.parse('Basic realm=foo Digest realm=bar')
end
def test_parse_multiple_blank
expected = [
challenge('Basic', { 'realm' => 'foo' }, 'Basic realm=foo'),
challenge('Digest', { 'realm' => 'bar' }, 'Digest realm=bar'),
]
assert_equal expected, @parser.parse('Basic realm=foo,, Digest realm=bar')
end
def test_parse_ntlm_init
expected = [
challenge('NTLM', nil, 'NTLM'),
]
assert_equal expected, @parser.parse('NTLM')
end
def test_parse_ntlm_type_2_3
expected = [
challenge('NTLM', 'foo=', 'NTLM foo='),
]
assert_equal expected, @parser.parse('NTLM foo=')
end
def test_parse_realm_uppercase
expected = [
challenge('Basic', { 'realm' => 'foo' }, 'Basic ReAlM=foo'),
]
assert_equal expected, @parser.parse('Basic ReAlM=foo')
end
def test_parse_realm_value_case
expected = [
challenge('Basic', { 'realm' => 'Foo' }, 'Basic realm=Foo'),
]
assert_equal expected, @parser.parse('Basic realm=Foo')
end
def test_parse_scheme_uppercase
expected = [
challenge('Basic', { 'realm' => 'foo' }, 'BaSiC realm=foo'),
]
assert_equal expected, @parser.parse('BaSiC realm=foo')
end
def test_parse_bad_whitespace_around_auth_param
expected = [
challenge('Basic', { 'realm' => 'foo' }, 'Basic realm = "foo"'),
]
assert_equal expected, @parser.parse('Basic realm = "foo"')
end
def test_parse_bad_single_quote
expected = [
challenge('Basic', { 'realm' => "'foo" }, "Basic realm='foo"),
]
assert_equal expected, @parser.parse("Basic realm='foo bar', qop='baz'")
end
def test_quoted_string
@parser.scanner = StringScanner.new '"text"'
string = @parser.quoted_string
assert_equal 'text', string
end
def test_quoted_string_bad
@parser.scanner = StringScanner.new '"text'
assert_nil @parser.quoted_string
end
def test_quoted_string_quote
@parser.scanner = StringScanner.new '"escaped \\" here"'
string = @parser.quoted_string
assert_equal 'escaped \\" here', string
end
def test_quoted_string_quote_end
@parser.scanner = StringScanner.new '"end \""'
string = @parser.quoted_string
assert_equal 'end \"', string
end
def test_token
@parser.scanner = StringScanner.new 'text'
string = @parser.token
assert_equal 'text', string
end
def test_token_space
@parser.scanner = StringScanner.new 't ext'
string = @parser.token
assert_equal 't', string
end
def test_token_special
@parser.scanner = StringScanner.new "t\text"
string = @parser.token
assert_equal 't', string
end
def challenge scheme, params, raw
Mechanize::HTTP::AuthChallenge.new scheme, params, raw
end
end
mechanize-2.10.1/test/test_mechanize_element_not_found_error.rb 0000644 0000041 0000041 00000000524 14645745627 025116 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeRedirectLimitReachedError < Mechanize::TestCase
def test_to_s
page = fake_page
error = Mechanize::ElementNotFoundError.new(page, :element, :conditions)
assert_match(/element/, error.to_s)
assert_match(/conditions/, error.to_s)
end
end
mechanize-2.10.1/test/test_mechanize_history.rb 0000644 0000041 0000041 00000003264 14645745627 021706 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeHistory < Mechanize::TestCase
def setup
super
@uri = URI 'http://example/'
@uri2 = @uri + '/a'
@history = Mechanize::History.new
end
def test_initialize
assert_empty @history
end
def test_clear
@history.push :page, @uri
@history.clear
assert_empty @history
end
def test_pop
assert_nil @history.pop
@history.push :page1, @uri
@history.push :page2, @uri2
assert_equal :page2, @history.pop
refute_empty @history
end
def test_push
p1 = page @uri
obj = @history.push p1
assert_same @history, obj
assert_equal 1, @history.length
p2 = page @uri2
@history.push p2
assert_equal 2, @history.length
end
def test_push_max_size
@history = Mechanize::History.new 2
@history.push :page1, @uri
assert_equal 1, @history.length
@history.push :page2, @uri
assert_equal 2, @history.length
@history.push :page3, @uri
assert_equal 2, @history.length
end
def test_push_uri
obj = @history.push :page, @uri
assert_same @history, obj
assert_equal 1, @history.length
@history.push :page2, @uri
assert_equal 2, @history.length
end
def test_shift
assert_nil @history.shift
@history.push :page1, @uri
@history.push :page2, @uri2
page = @history.shift
assert_equal :page1, page
refute_empty @history
@history.shift
assert_empty @history
end
def test_visited_eh
refute @history.visited? @uri
@history.push page @uri
assert @history.visited? URI('http://example')
assert @history.visited? URI('http://example/')
end
end
mechanize-2.10.1/test/test_mechanize_http_agent.rb 0000644 0000041 0000041 00000143130 14645745627 022337 0 ustar www-data www-data # coding: utf-8
# frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeHttpAgent < Mechanize::TestCase
def setup
super
@agent = @mech.agent
@uri = URI.parse 'http://example/'
@req = Net::HTTP::Get.new '/'
@res = Net::HTTPOK.allocate
@res.instance_variable_set :@code, 200
@res.instance_variable_set :@header, {}
@headers = %w[accept accept-encoding user-agent]
end
def auth_realm uri, scheme, type
base_uri = uri + '/'
realm = Mechanize::HTTP::AuthRealm.new scheme, base_uri, 'r'
@agent.authenticate_methods[base_uri][type] << realm
realm
end
def skip_if_jruby_zlib
if RUBY_ENGINE == 'jruby'
meth = caller_locations(1,1).first.base_label
skip "#{meth}: skipped because how Zlib handles error is different in JRuby"
end
end
def test_agent_is_named
assert_equal 'mechanize', Mechanize::HTTP::Agent.new.http.name
assert_equal 'unique', Mechanize::HTTP::Agent.new('unique').http.name
end
def test_auto_io
Tempfile.open 'input' do |input_io|
input_io.binmode
input_io.write '12345'
input_io.rewind
out_io = @agent.auto_io @NAME, 1024, input_io
assert_equal '12345', out_io.string
assert_equal Encoding::BINARY, out_io.string.encoding if
Object.const_defined? :Encoding
end
end
def test_auto_io_chunk
Tempfile.open 'input' do |input_io|
chunks = []
input_io.binmode
input_io.write '12345'
input_io.rewind
@agent.auto_io @NAME, 1, input_io do |chunk|
chunks << chunk
end
assert_equal %w[1 2 3 4 5], chunks
end
end
def test_auto_io_tempfile
@agent.max_file_buffer = 3
Tempfile.open 'input' do |input_io|
input_io.binmode
input_io.write '12345'
input_io.rewind
out_io = @agent.auto_io @NAME, 1, input_io
result = out_io.read
assert_equal '12345', result
assert_equal Encoding::BINARY, result.encoding if
Object.const_defined? :Encoding
end
end
def test_auto_io_yield
Tempfile.open 'input' do |input_io|
input_io.binmode
input_io.write '12345'
input_io.rewind
out_io = @agent.auto_io @NAME, 1024, input_io do |chunk|
"x#{chunk}"
end
assert_equal 'x12345', out_io.string
end
end
def test_certificate_equals
cert_path = File.expand_path '../data/server.crt', __FILE__
cert = OpenSSL::X509::Certificate.new File.read cert_path
@agent.certificate = cert
assert_equal cert.to_pem, @agent.certificate.to_pem
end
def test_certificate_equals_file
cert_path = File.expand_path '../data/server.crt', __FILE__
cert = OpenSSL::X509::Certificate.new File.read cert_path
@agent.certificate = cert_path
assert_equal cert.to_pem, @agent.certificate.to_pem
end
def test_connection_for_file
uri = URI.parse 'file:///nonexistent'
conn = @agent.connection_for uri
assert_equal Mechanize::FileConnection.new, conn
end
def test_connection_for_http
conn = @agent.connection_for @uri
assert_equal @agent.http, conn
end
def test_disable_keep_alive
@agent.disable_keep_alive @req
refute @req['connection']
end
def test_disable_keep_alive_no
@agent.keep_alive = false
@agent.disable_keep_alive @req
assert_equal 'close', @req['connection']
end
def test_enable_gzip
@agent.enable_gzip @req
assert_equal 'gzip,deflate,identity', @req['accept-encoding']
end
def test_enable_gzip_no
@agent.gzip_enabled = false
@agent.enable_gzip @req
assert_equal 'identity', @req['accept-encoding']
end
def test_fetch_file_nonexistent
in_tmpdir do
nonexistent = File.join Dir.pwd, 'nonexistent'
uri = URI.parse "file:///#{nonexistent}"
e = assert_raises Mechanize::ResponseCodeError do
@agent.fetch uri
end
assert_match "404 => Net::HTTPNotFound for #{uri}", e.message
end
end
def test_fetch_file_plus
Tempfile.open '++plus++' do |io|
content = 'plusses +++'
io.write content
io.rewind
uri = URI.parse "file://#{Mechanize::Util.uri_escape io.path}"
page = @agent.fetch uri
assert_equal content, page.body
assert_kind_of Mechanize::File, page
end
end
def test_fetch_file_space
foo = File.expand_path("../htdocs/dir with spaces/foo.html", __FILE__)
uri = URI.parse "file://#{Mechanize::Util.uri_escape foo}"
page = @agent.fetch uri
assert_equal File.read(foo), page.body.gsub(/\r\n/, "\n")
assert_kind_of Mechanize::Page, page
end
def test_fetch_head_gzip
uri = @uri + '/gzip?file=index.html'
page = @agent.fetch uri, :head
assert_kind_of Mechanize::Page, page
end
def test_fetch_hooks
@agent.pre_connect_hooks << proc do |agent, request|
assert_equal '/index.html', request.path
assert_equal @agent, agent
end
@agent.post_connect_hooks << proc do |agent, uri, response, body|
assert_equal @agent, agent
assert_equal URI('http://example/index.html'), uri
assert_equal '200', response.code
assert_kind_of String, body
end
@agent.fetch URI 'http://example/index.html'
end
def test_fetch_ignore_bad_chunking
@agent.ignore_bad_chunking = true
file = @agent.fetch 'http://example/bad_chunking'
assert_equal '0123456789', file.content
end
def test_fetch_post_connect_hook
response = nil
@agent.post_connect_hooks << lambda { |_, _, res, _| response = res }
@agent.fetch 'http://localhost/'
assert response
end
def test_fetch_redirect_header
page = @agent.fetch('http://example/redirect', :get,
'X-Location' => '/http_headers',
'Range' => 'bytes=0-99999')
assert_match 'range|bytes=0-999', page.body
end
def test_fetch_server_error
e = assert_raises Mechanize::ResponseCodeError do
@mech.get 'http://localhost/response_code?code=500'
end
assert_equal '500', e.response_code
end
def test_fetch_allowed_error_codes
@agent.allowed_error_codes = ['500']
page = @mech.get 'http://localhost/response_code?code=500'
assert_equal '500', page.code
end
def test_fetch_allowed_error_codes_int
@agent.allowed_error_codes = [500]
page = @mech.get 'http://localhost/response_code?code=500'
assert_equal '500', page.code
end
def test_get_meta_refresh_header_follow_self
@agent.follow_meta_refresh = true
@agent.follow_meta_refresh_self = true
page = Mechanize::Page.new(@uri, nil, '', 200, @mech)
@res.instance_variable_set :@header, 'refresh' => ['0']
refresh = @agent.get_meta_refresh @res, @uri, page
assert_equal [0.0, URI('http://example/')], refresh
end
def test_get_meta_refresh_header_no_follow
page = Mechanize::Page.new(@uri, nil, '', 200, @mech)
@res.instance_variable_set :@header, 'refresh' => ['0']
refresh = @agent.get_meta_refresh @res, @uri, page
assert_nil refresh
end
def test_get_meta_refresh_header_no_follow_self
@agent.follow_meta_refresh = true
page = Mechanize::Page.new(@uri, nil, '', 200, @mech)
@res.instance_variable_set :@header, 'refresh' => ['0']
refresh = @agent.get_meta_refresh @res, @uri, page
assert_nil refresh
end
def test_get_meta_refresh_meta_follow_self
@agent.follow_meta_refresh = true
@agent.follow_meta_refresh_self = true
body = <<-BODY
BODY
page = Mechanize::Page.new(@uri, nil, body, 200, @mech)
refresh = @agent.get_meta_refresh @res, @uri, page
assert_equal [0, nil], refresh
end
def test_get_meta_refresh_meta_no_follow
body = <<-BODY
BODY
page = Mechanize::Page.new(@uri, nil, body, 200, @mech)
refresh = @agent.get_meta_refresh @res, @uri, page
assert_nil refresh
end
def test_get_meta_refresh_meta_no_follow_self
@agent.follow_meta_refresh = true
body = <<-BODY
BODY
page = Mechanize::Page.new(@uri, nil, body, 200, @mech)
refresh = @agent.get_meta_refresh @res, @uri, page
assert_nil refresh
end
def test_get_robots
robotstxt = @agent.get_robots 'http://localhost/robots.txt'
refute_equal '', robotstxt
robotstxt = @agent.get_robots 'http://localhost/response_code?code=404'
assert_equal '', robotstxt
end
def test_hook_content_encoding_response
@mech.content_encoding_hooks << lambda{|agent, uri, response, response_body_io|
response['content-encoding'] = 'gzip' if response['content-encoding'] == 'agzip'}
@res.instance_variable_set :@header, 'content-encoding' => %w[agzip]
body_io = StringIO.new 'part'
@agent.hook_content_encoding @res, @uri, body_io
assert_equal 'gzip', @res['content-encoding']
end
def test_http_request_file
uri = URI.parse 'file:///nonexistent'
request = @agent.http_request uri, :get
assert_kind_of Mechanize::FileRequest, request
assert_equal '/nonexistent', request.path
end
def test_http_request_get
request = @agent.http_request @uri, :get
assert_kind_of Net::HTTP::Get, request
assert_equal '/', request.path
end
def test_http_request_post
request = @agent.http_request @uri, :post
assert_kind_of Net::HTTP::Post, request
assert_equal '/', request.path
end
def test_idle_timeout_equals
@agent.idle_timeout = 1
assert_equal 1, @agent.http.idle_timeout
end
def test_inflate
body_io = StringIO.new "x\x9C+H,*\x01\x00\x04?\x01\xB8"
result = @agent.inflate body_io
assert_equal 'part', result.read
end
def test_post_connect
@agent.post_connect_hooks << proc { |agent, uri, response, body|
assert_equal @agent, agent
assert_equal @res, response
assert_equal 'body', body
throw :called
}
io = StringIO.new 'body'
assert_throws :called do
@agent.post_connect @uri, @res, io
end
assert_equal 0, io.pos
end
def test_pre_connect
@agent.pre_connect_hooks << proc { |agent, request|
assert_equal @agent, agent
assert_equal @req, request
throw :called
}
assert_throws :called do
@agent.pre_connect @req
end
end
def test_request_add_headers
@agent.request_add_headers @req, 'Content-Length' => 300
assert_equal '300', @req['content-length']
end
def test_request_add_headers_etag
@agent.request_add_headers @req, :etag => '300'
assert_equal '300', @req['etag']
end
def test_request_add_headers_if_modified_since
@agent.request_add_headers @req, :if_modified_since => 'some_date'
assert_equal 'some_date', @req['if-modified-since']
end
def test_request_add_headers_none
@agent.request_add_headers @req
assert_equal @headers, @req.to_hash.keys.sort
end
def test_request_add_headers_request_headers
@agent.request_headers['X-Foo'] = 'bar'
@agent.request_add_headers @req
assert_equal @headers + %w[x-foo], @req.to_hash.keys.sort
end
def test_request_add_headers_symbol
e = assert_raises ArgumentError do
@agent.request_add_headers @req, :content_length => 300
end
assert_equal 'unknown header symbol content_length', e.message
end
def test_request_auth_basic
@agent.add_auth @uri, 'user', 'password'
auth_realm @uri, 'Basic', :basic
@agent.request_auth @req, @uri
assert_match %r%^Basic %, @req['Authorization']
end
def test_request_auth_digest
@agent.add_auth @uri, 'user', 'password'
realm = auth_realm @uri, 'Digest', :digest
@agent.digest_challenges[realm] = 'Digest realm=r, qop="auth"'
@agent.request_auth @req, @uri
assert_match %r%^Digest %, @req['Authorization']
assert_match %r%qop=auth%, @req['Authorization']
@req['Authorization'] = nil
@agent.request_auth @req, @uri
assert_match %r%^Digest %, @req['Authorization']
assert_match %r%qop=auth%, @req['Authorization']
end
def test_request_auth_iis_digest
@agent.add_auth @uri, 'user', 'password'
realm = auth_realm @uri, 'Digest', :digest
@agent.digest_challenges[realm] = 'Digest realm=r, qop="auth"'
@agent.request_auth @req, @uri
assert_match %r%^Digest %, @req['Authorization']
assert_match %r%qop=auth%, @req['Authorization']
end
def test_request_auth_none
@agent.request_auth @req, @uri
assert_nil @req['Authorization']
end
def test_request_cookies
uri = URI.parse 'http://host.example.com'
@agent.cookie_jar.parse 'hello=world domain=.example.com', uri
@agent.request_cookies @req, uri
assert_equal 'hello="world domain=.example.com"', @req['Cookie']
end
def test_request_cookies_many
uri = URI.parse 'http://host.example.com'
cookie_str = 'a=b domain=.example.com, c=d domain=.example.com'
@agent.cookie_jar.parse cookie_str, uri
@agent.request_cookies @req, uri
expected_variant1 = /a="b domain=\.example\.com"; c="d domain=\.example\.com"/
expected_variant2 = /c="d domain=\.example\.com"; a="b domain=\.example\.com"/
assert_match(/^(#{expected_variant1}|#{expected_variant2})$/, @req['Cookie'])
end
def test_request_cookies_none
@agent.request_cookies @req, @uri
assert_nil @req['Cookie']
end
def test_request_cookies_wrong_domain
uri = URI.parse 'http://host.example.com'
@agent.cookie_jar.parse 'hello=world domain=.example.com', uri
@agent.request_cookies @req, @uri
assert_nil @req['Cookie']
end
def test_request_host
@agent.request_host @req, @uri
assert_equal 'example', @req['host']
end
def test_request_host_nonstandard
@uri.port = 81
@agent.request_host @req, @uri
assert_equal 'example:81', @req['host']
end
def test_request_language_charset
@agent.request_language_charset @req
assert_equal 'en-us,en;q=0.5', @req['accept-language']
assert_equal 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', @req['accept-charset']
end
def test_request_referer
referer = URI.parse 'http://old.example'
@agent.request_referer @req, @uri, referer
assert_equal 'http://old.example', @req['referer']
end
def test_request_referer_https
uri = URI.parse 'https://example'
referer = URI.parse 'https://old.example'
@agent.request_referer @req, uri, referer
assert_equal 'https://old.example', @req['referer']
end
def test_request_referer_https_downgrade
referer = URI.parse 'https://old.example'
@agent.request_referer @req, @uri, referer
assert_nil @req['referer']
end
def test_request_referer_https_downgrade_case
uri = URI.parse 'http://example'
referer = URI.parse 'httpS://old.example'
@agent.request_referer @req, uri, referer
assert_nil @req['referer']
end
def test_request_referer_https_upgrade
uri = URI.parse 'https://example'
referer = URI.parse 'http://old.example'
@agent.request_referer @req, uri, referer
assert_equal 'http://old.example', @req['referer']
end
def test_request_referer_none
@agent.request_referer @req, @uri, nil
assert_nil @req['referer']
end
def test_request_referer_strip
uri = URI.parse 'http://example.com/index.html'
host_path = "old.example/page.html?q=x"
referer = "http://#{host_path}"
[
"",
"@",
"user1@",
":@",
"user1:@",
":password1@",
"user1:password1@",
].each { |userinfo|
['', '#frag'].each { |frag|
url = URI.parse "http://#{userinfo}#{host_path}#{frag}"
@agent.request_referer @req, uri, url
assert_equal referer, @req['referer'], url
}
}
end
def test_request_user_agent
@agent.request_user_agent @req
assert_match %r%^Mechanize/#{Mechanize::VERSION}%, @req['user-agent']
ruby_version = if RUBY_PATCHLEVEL >= 0 then
"#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
else
"#{RUBY_VERSION}dev#{RUBY_REVISION}"
end
assert_match %r%Ruby/#{ruby_version}%, @req['user-agent']
end
def test_resolve_bad_uri
e = assert_raises ArgumentError do
@agent.resolve 'google'
end
assert_equal 'absolute URL needed (not google)', e.message
end
def test_resolve_uri_without_path
e = assert_raises ArgumentError do
@agent.resolve 'http:%5C%5Cfoo'
end
assert_equal 'hierarchical URL needed (not http:%5C%5Cfoo)', e.message
end
def test_resolve_utf8
uri = 'http://example?q=ü'
resolved = @agent.resolve uri
assert_equal '/?q=%C3%BC', resolved.request_uri
end
def test_resolve_parameters_body
input_params = { :q => 'hello' }
uri, params = @agent.resolve_parameters @uri, :post, input_params
assert_equal 'http://example/', uri.to_s
assert_equal input_params, params
end
def test_resolve_parameters_query
uri, params = @agent.resolve_parameters @uri, :get, :q => 'hello'
assert_equal 'http://example/?q=hello', uri.to_s
assert_nil params
end
def test_resolve_parameters_query_append
input_params = { :q => 'hello' }
@uri.query = 'a=b'
uri, params = @agent.resolve_parameters @uri, :get, input_params
assert_equal 'http://example/?a=b&q=hello', uri.to_s
assert_nil params
end
def test_resolve_slashes
page = Mechanize::Page.new URI('http://example/foo/'), nil, '', 200, @mech
uri = '/bar/http://example/test/'
resolved = @agent.resolve uri, page
assert_equal 'http://example/bar/http://example/test/', resolved.to_s
end
def test_response_authenticate
@agent.add_auth @uri, 'user', 'password'
@res.instance_variable_set :@header, 'www-authenticate' => ['Basic realm=r']
@agent.response_authenticate @res, nil, @uri, @req, {}, nil, nil
base_uri = @uri + '/'
realm = Mechanize::HTTP::AuthRealm.new 'Basic', base_uri, 'r'
assert_equal [realm], @agent.authenticate_methods[base_uri][:basic]
end
def test_response_authenticate_digest
@agent.add_auth @uri, 'user', 'password'
@res.instance_variable_set(:@header,
'www-authenticate' => ['Digest realm=r'])
@agent.response_authenticate @res, nil, @uri, @req, {}, nil, nil
base_uri = @uri + '/'
realm = Mechanize::HTTP::AuthRealm.new 'Digest', base_uri, 'r'
assert_equal [realm], @agent.authenticate_methods[base_uri][:digest]
challenge = Mechanize::HTTP::AuthChallenge.new('Digest',
{ 'realm' => 'r' },
'Digest realm=r')
assert_equal challenge, @agent.digest_challenges[realm]
end
def test_response_authenticate_digest_iis
@agent.add_auth @uri, 'user', 'password'
@res.instance_variable_set(:@header,
'www-authenticate' => ['Digest realm=r'],
'server' => ['Microsoft-IIS'])
@agent.response_authenticate @res, nil, @uri, @req, {}, nil, nil
base_uri = @uri + '/'
realm = Mechanize::HTTP::AuthRealm.new 'Digest', base_uri, 'r'
assert_equal [realm], @agent.authenticate_methods[base_uri][:iis_digest]
end
def test_response_authenticate_multiple
@agent.add_auth @uri, 'user', 'password'
@res.instance_variable_set(:@header,
'www-authenticate' =>
['Basic realm=r, Digest realm=r'])
@agent.response_authenticate @res, nil, @uri, @req, {}, nil, nil
base_uri = @uri + '/'
realm = Mechanize::HTTP::AuthRealm.new 'Digest', base_uri, 'r'
assert_equal [realm], @agent.authenticate_methods[base_uri][:digest]
assert_empty @agent.authenticate_methods[base_uri][:basic]
end
def test_response_authenticate_no_credentials
@res.instance_variable_set :@header, 'www-authenticate' => ['Basic realm=r']
e = assert_raises Mechanize::UnauthorizedError do
@agent.response_authenticate @res, fake_page, @uri, @req, {}, nil, nil
end
assert_match 'no credentials', e.message
assert_match 'available realms: r', e.message
end
def test_response_authenticate_no_www_authenticate
@agent.add_auth @uri, 'user', 'password'
denied_uri = URI('http://example/denied')
denied = page denied_uri, 'text/html', '', 401
e = assert_raises Mechanize::UnauthorizedError do
@agent.response_authenticate @res, denied, @uri, @req, {}, nil, nil
end
assert_equal "401 => Net::HTTPUnauthorized for #{denied_uri} -- " \
"WWW-Authenticate header missing in response",
e.message
end
def test_response_authenticate_ntlm
@uri += '/ntlm'
@agent.add_auth @uri, 'user', 'password'
@res.instance_variable_set(:@header,
'www-authenticate' => ['Negotiate, NTLM'])
begin
page = @agent.response_authenticate @res, nil, @uri, @req, {}, nil, nil
rescue OpenSSL::Digest::DigestError
skip "It looks like OpenSSL is not configured to support MD4"
end
assert_equal 'ok', page.body # lame test
end
def test_response_authenticate_unknown
@agent.add_auth @uri, 'user', 'password'
page = Mechanize::File.new nil, nil, nil, 401
@res.instance_variable_set(:@header,
'www-authenticate' => ['Unknown realm=r'])
assert_raises Mechanize::UnauthorizedError do
@agent.response_authenticate @res, page, @uri, @req, nil, nil, nil
end
end
def test_response_content_encoding_7_bit
@res.instance_variable_set :@header, 'content-encoding' => %w[7bit]
body = @agent.response_content_encoding @res, StringIO.new('part')
assert_equal 'part', body.read
end
def test_response_content_encoding_deflate
@res.instance_variable_set :@header, 'content-encoding' => %w[deflate]
body_io = StringIO.new "x\x9C+H,*\x01\x00\x04?\x01\xB8"
body = @agent.response_content_encoding @res, body_io
assert_equal 'part', body.read
assert body_io.closed?
end
def test_response_content_encoding_deflate_chunked
@res.instance_variable_set :@header, 'content-encoding' => %w[deflate]
body_io = StringIO.new "x\x9C+H,*\x01\x00\x04?\x01\xB8"
body = @agent.response_content_encoding @res, body_io
assert_equal 'part', body.read
end
def test_response_content_encoding_deflate_corrupt
@res.instance_variable_set :@header, 'content-encoding' => %w[deflate]
body_io = StringIO.new "x\x9C+H,*\x01\x00\x04?\x01" # missing 1 byte
e = assert_raises Mechanize::Error do
@agent.response_content_encoding @res, body_io
end
assert_match %r%error handling content-encoding deflate:%, e.message
assert_match %r%Zlib%, e.message
assert body_io.closed?
end
def test_response_content_encoding_deflate_empty
@res.instance_variable_set :@header, 'content-encoding' => %w[deflate]
body = @agent.response_content_encoding @res, StringIO.new
assert_equal '', body.read
end
# IIS/6.0 ASP.NET/2.0.50727 does not wrap deflate with zlib, WTF?
def test_response_content_encoding_deflate_no_zlib
@res.instance_variable_set :@header, 'content-encoding' => %w[deflate]
body = @agent.response_content_encoding @res, StringIO.new("+H,*\001\000")
assert_equal 'part', body.read
end
def test_response_content_encoding_gzip
@res.instance_variable_set :@header, 'content-encoding' => %w[gzip]
body_io = StringIO.new \
"\037\213\b\0002\002\225M\000\003+H,*\001\000\306p\017I\004\000\000\000"
body = @agent.response_content_encoding @res, body_io
assert_equal 'part', body.read
assert body_io.closed?
end
def test_response_content_encoding_gzip_chunked
def @res.content_length() nil end
@res.instance_variable_set :@header, 'content-encoding' => %w[gzip]
body_io = StringIO.new \
"\037\213\b\0002\002\225M\000\003+H,*\001\000\306p\017I\004\000\000\000"
body = @agent.response_content_encoding @res, body_io
assert_equal 'part', body.read
end
def test_response_content_encoding_gzip_corrupt
log = StringIO.new
logger = Logger.new log
@agent.context.log = logger
@res.instance_variable_set :@header, 'content-encoding' => %w[gzip]
body_io = StringIO.new \
"\037\213\b\0002\002\225M\000\003+H,*\001"
skip_if_jruby_zlib
e = assert_raises Mechanize::Error do
@agent.response_content_encoding @res, body_io
end
assert_match %r%error handling content-encoding gzip:%, e.message
assert_match %r%Zlib%, e.message
assert_match %r%unable to gunzip response: unexpected end of file%,
log.string
assert_match %r%unable to inflate response: buffer error%,
log.string
assert body_io.closed?
end
def test_response_content_encoding_gzip_checksum_corrupt_crc
log = StringIO.new
logger = Logger.new log
@agent.context.log = logger
@res.instance_variable_set :@header, 'content-encoding' => %w[gzip]
body_io = StringIO.new \
"\037\213\b\0002\002\225M\000\003+H,*\001\000\306p\017J\004\000\000\000"
body = @agent.response_content_encoding @res, body_io
assert_equal 'part', body.read
assert body_io.closed?
assert_match %r%invalid compressed data -- crc error%, log.string
rescue IOError
skip_if_jruby_zlib
raise
end
def test_response_content_encoding_gzip_checksum_corrupt_length
log = StringIO.new
logger = Logger.new log
@agent.context.log = logger
@res.instance_variable_set :@header, 'content-encoding' => %w[gzip]
body_io = StringIO.new \
"\037\213\b\0002\002\225M\000\003+H,*\001\000\306p\017I\005\000\000\000"
@agent.response_content_encoding @res, body_io
assert body_io.closed?
assert_match %r%invalid compressed data -- length error%, log.string
rescue IOError
skip_if_jruby_zlib
raise
end
def test_response_content_encoding_gzip_checksum_truncated
log = StringIO.new
logger = Logger.new log
@agent.context.log = logger
@res.instance_variable_set :@header, 'content-encoding' => %w[gzip]
body_io = StringIO.new \
"\037\213\b\0002\002\225M\000\003+H,*\001\000\306p\017I\004\000\000"
@agent.response_content_encoding @res, body_io
assert body_io.closed?
assert_match %r%unable to gunzip response: footer is not found%, log.string
rescue IOError
skip_if_jruby_zlib
raise
end
def test_response_content_encoding_gzip_empty
@res.instance_variable_set :@header, 'content-encoding' => %w[gzip]
body = @agent.response_content_encoding @res, StringIO.new
assert_equal '', body.read
end
def test_response_content_encoding_gzip_encoding_bad
@res.instance_variable_set(:@header,
'content-encoding' => %w[gzip],
'content-type' => 'text/html; charset=UTF-8')
# "test\xB2"
body_io = StringIO.new \
"\037\213\b\000*+\314N\000\003+I-.\331\004\000x\016\003\376\005\000\000\000"
body = @agent.response_content_encoding @res, body_io
expected = +"test\xB2"
expected.force_encoding Encoding::BINARY if have_encoding?
content = body.read
assert_equal expected, content
assert_equal Encoding::BINARY, content.encoding if have_encoding?
end
def test_response_content_encoding_gzip_no_footer
@res.instance_variable_set :@header, 'content-encoding' => %w[gzip]
body_io = StringIO.new \
"\037\213\b\0002\002\225M\000\003+H,*\001\000"
body = @agent.response_content_encoding @res, body_io
assert_equal 'part', body.read
assert body_io.closed?
rescue IOError
skip_if_jruby_zlib
raise
end
def test_response_content_encoding_none
@res.instance_variable_set :@header, 'content-encoding' => %w[none]
body = @agent.response_content_encoding @res, StringIO.new('part')
assert_equal 'part', body.read
end
def test_response_content_encoding_empty_string
@res.instance_variable_set :@header, 'content-encoding' => %w[]
body = @agent.response_content_encoding @res, StringIO.new('part')
assert_equal 'part', body.read
end
def test_response_content_encoding_identity
@res.instance_variable_set :@header, 'content-encoding' => %w[identity]
body = @agent.response_content_encoding @res, StringIO.new('part')
assert_equal 'part', body.read
end
def test_response_content_encoding_tempfile_7_bit
body_io = tempfile 'part'
@res.instance_variable_set :@header, 'content-encoding' => %w[7bit]
body = @agent.response_content_encoding @res, body_io
assert_equal 'part', body.read
refute body_io.closed?
ensure
begin
body_io.close! if body_io and not body_io.closed?
rescue IOError
# HACK for ruby 1.8
end
end
def test_response_content_encoding_tempfile_gzip
body_io = tempfile "x\x9C+H,*\x01\x00\x04?\x01\xB8"
@res.instance_variable_set :@header, 'content-encoding' => %w[deflate]
body = @agent.response_content_encoding @res, body_io
assert_equal 'part', body.read
assert body_io.closed?
ensure
body_io.close! if body_io and not body_io.closed?
end
def test_response_content_encoding_unknown
@res.instance_variable_set :@header, 'content-encoding' => %w[unknown]
body = StringIO.new 'part'
e = assert_raises Mechanize::Error do
@agent.response_content_encoding @res, body
end
assert_equal 'unsupported content-encoding: unknown', e.message
end
def test_response_content_encoding_x_gzip
@res.instance_variable_set :@header, 'content-encoding' => %w[x-gzip]
body_io = StringIO.new \
"\037\213\b\0002\002\225M\000\003+H,*\001\000\306p\017I\004\000\000\000"
body = @agent.response_content_encoding @res, body_io
assert_equal 'part', body.read
end
def test_response_cookies
uri = URI.parse 'http://host.example.com'
cookie_str = 'a=b domain=.example.com'
@res.instance_variable_set(:@header,
'set-cookie' => [cookie_str],
'content-type' => %w[text/html])
page = Mechanize::Page.new uri, @res, '', 200, @mech
@agent.response_cookies @res, uri, page
assert_equal ['a="b domain=.example.com"'],
@agent.cookie_jar.cookies(uri).map { |c| c.to_s }
end
def test_response_cookies_many
uri = URI.parse 'http://host.example.com'
cookie1 = 'a=b domain=.example.com'
cookie2 = 'c=d domain=.example.com'
cookies = [cookie1, cookie2]
@res.instance_variable_set(:@header,
'set-cookie' => cookies,
'content-type' => %w[text/html])
page = Mechanize::Page.new uri, @res, '', 200, @mech
@agent.response_cookies @res, uri, page
cookies_from_jar = @agent.cookie_jar.cookies(uri)
assert_equal 2, cookies_from_jar.length
assert_equal [
'a="b domain=.example.com"',
'c="d domain=.example.com"',
], cookies_from_jar.sort_by { |c| c.name }.map(&:to_s)
end
def test_response_cookies_meta
uri = URI.parse 'http://host.example.com'
cookie_str = 'a=b domain=.example.com'
body = <<-BODY
"
BODY
@res.instance_variable_set(:@header,
'content-type' => %w[text/html])
page = Mechanize::Page.new uri, @res, body, 200, @mech
@agent.response_cookies @res, uri, page
assert_equal ['a="b domain=.example.com"'],
@agent.cookie_jar.cookies(uri).map { |c| c.to_s }
end
def test_response_cookies_meta_bogus
uri = URI.parse 'http://host.example.com'
body = <<-BODY
"
BODY
@res.instance_variable_set(:@header,
'content-type' => %w[text/html])
page = Mechanize::Page.new uri, @res, body, 200, @mech
@agent.response_cookies @res, uri, page
assert_empty @agent.cookie_jar.cookies(uri)
end
def test_response_follow_meta_refresh
uri = URI.parse 'http://example/#id+1'
body = <<-BODY
BODY
page = Mechanize::Page.new(uri, nil, body, 200, @mech)
@agent.follow_meta_refresh = true
@agent.follow_meta_refresh_self = true
page = @agent.response_follow_meta_refresh @res, uri, page, 0
assert_equal uri, page.uri
end
def test_response_follow_meta_refresh_limit
uri = URI.parse 'http://example/#id+1'
body = <<-BODY
BODY
page = Mechanize::Page.new(uri, nil, body, 200, @mech)
@agent.follow_meta_refresh = true
@agent.follow_meta_refresh_self = true
assert_raises Mechanize::RedirectLimitReachedError do
@agent.response_follow_meta_refresh(@res, uri, page,
@agent.redirection_limit)
end
end
def test_response_meta_refresh_with_insecure_url
uri = URI.parse 'http://example/#id+1'
body = <<-BODY
BODY
page = Mechanize::Page.new(uri, nil, body, 200, @mech)
@agent.follow_meta_refresh = true
assert_raises Mechanize::Error do
@agent.response_follow_meta_refresh(@res, uri, page,
@agent.redirection_limit)
end
end
def test_response_parse
body = 'hi'
@res.instance_variable_set :@header, 'content-type' => %w[text/html]
page = @agent.response_parse @res, body, @uri
assert_instance_of Mechanize::Page, page
assert_equal @mech, page.mech
end
def test_response_parse_content_type_case
body = 'hi'
@res.instance_variable_set(:@header, 'content-type' => %w[text/HTML])
page = @agent.response_parse @res, body, @uri
assert_instance_of Mechanize::Page, page
assert_equal 'text/HTML', page.content_type
end
def test_response_parse_content_type_encoding
body = 'hi'
@res.instance_variable_set(:@header,
'content-type' =>
%w[text/html;charset=ISO-8859-1])
page = @agent.response_parse @res, body, @uri
assert_instance_of Mechanize::Page, page
assert_equal @mech, page.mech
assert_equal 'ISO-8859-1', page.encoding
assert_equal 'ISO-8859-1', page.parser.encoding
end
def test_response_parse_content_type_encoding_broken_iso_8859_1
body = 'hi'
@res.instance_variable_set(:@header,
'content-type' =>
%w[text/html; charset=ISO_8859-1])
page = @agent.response_parse @res, body, @uri
assert_instance_of Mechanize::Page, page
assert_equal 'ISO_8859-1', page.encoding
end
def test_response_parse_content_type_encoding_broken_utf_8
body = 'hi'
@res.instance_variable_set(:@header,
'content-type' =>
%w[text/html; charset=UTF8])
page = @agent.response_parse @res, body, @uri
assert_instance_of Mechanize::Page, page
# as of libxml 2.12.0, the result is dependent on how libiconv is built (which aliases are supported)
# if the alias "UTF8" is defined, then the result will be "UTF-8".
# if the alias "UTF8" is not defined, then the result will be "UTF8".
# note that this alias may be defined by Nokogiri itself in its EncodingHandler class.
assert_includes ["UTF8", "UTF-8"], page.encoding
assert_includes ["UTF8", "UTF-8"], page.parser.encoding
end
def test_response_parse_content_type_encoding_garbage
body = 'hi'
@res.instance_variable_set(:@header,
'content-type' =>
%w[text/html; charset=garbage_charset])
page = @agent.response_parse @res, body, @uri
assert_instance_of Mechanize::Page, page
assert_equal @mech, page.mech
end
def test_response_parse_content_type_encoding_semicolon
body = 'hi'
@res.instance_variable_set(:@header,
'content-type' =>
%w[text/html;charset=UTF-8;])
page = @agent.response_parse @res, body, @uri
assert_instance_of Mechanize::Page, page
assert_equal 'UTF-8', page.encoding
end
def test_response_read
def @res.read_body() yield 'part' end
def @res.content_length() 4 end
io = @agent.response_read @res, @req, @uri
body = io.read
assert_equal 'part', body
assert_equal Encoding::BINARY, body.encoding
end
def test_response_read_chunked_no_trailer
@res['Transfer-Encoding'] = 'chunked'
def @res.content_length() end
def @res.read_body
yield 'a' * 10
raise EOFError
end
e = assert_raises Mechanize::ChunkedTerminationError do
@agent.response_read @res, @req, @uri
end
assert_equal 'aaaaaaaaaa', e.body_io.read
end
def test_response_read_content_length_head
req = Net::HTTP::Head.new '/'
def @res.content_length() end
def @res.read_body() end
io = @agent.response_read @res, req, @uri
assert_equal '', io.read
end
def test_response_read_content_length_mismatch
def @res.content_length() 5 end
def @res.read_body() yield 'part' end
e = assert_raises Mechanize::ResponseReadError do
@agent.response_read @res, @req, @uri
end
assert_equal 'Content-Length (5) does not match response body length (4)' \
' (Mechanize::ResponseReadError)', e.message
end
def test_response_read_content_length_redirect
res = Net::HTTPFound.allocate
def res.content_length() 5 end
def res.code() 302 end
def res.read_body() yield 'part' end
res.instance_variable_set :@header, {}
io = @agent.response_read res, @req, @uri
assert_equal 'part', io.read
end
def test_response_read_error
def @res.read_body()
yield 'part'
raise Net::HTTP::Persistent::Error
end
e = assert_raises Mechanize::ResponseReadError do
@agent.response_read @res, @req, @uri
end
assert_equal @res, e.response
assert_equal 'part', e.body_io.read
assert_kind_of Net::HTTP::Persistent::Error, e.error
end
def test_response_read_file
Tempfile.open 'pi.txt' do |tempfile|
tempfile.write "π\n"
tempfile.flush
tempfile.rewind
uri = URI.parse "file://#{tempfile.path}"
req = Mechanize::FileRequest.new uri
res = Mechanize::FileResponse.new tempfile.path
io = @agent.response_read res, req, uri
expected = "π\n".dup.force_encoding(Encoding::BINARY)
# Ruby 1.8.7 doesn't let us set the write mode of the tempfile to binary,
# so we should expect an inserted carriage return on some platforms
expected_with_carriage_return = "π\r\n".dup.force_encoding(Encoding::BINARY)
body = io.read
assert_match(/^(#{expected}|#{expected_with_carriage_return})$/m, body)
assert_equal Encoding::BINARY, body.encoding
end
end
def test_response_read_large
@agent.max_file_buffer = 10240
def @res.read_body() yield 'a' * 10241 end
def @res.content_length() 10241 end
io = @agent.response_read @res, @req, @uri
assert_kind_of Tempfile, io
assert_equal 10241, io.stat.size
end
def test_response_read_large_chunked
@agent.max_file_buffer = 10240
def @res.read_body
11.times do yield 'a' * 1024 end
end
def @res.content_length() end
io = @agent.response_read @res, @req, @uri
assert_kind_of Tempfile, io
assert_equal 11264, io.stat.size
end
def test_response_read_no_body
req = Net::HTTP::Options.new '/'
def @res.content_length() end
def @res.read_body() end
io = @agent.response_read @res, req, @uri
assert_equal '', io.read
end
def test_response_read_unknown_code
res = Net::HTTPUnknownResponse.allocate
res.instance_variable_set :@code, 9999
res.instance_variable_set :@header, {}
def res.read_body() yield 'part' end
e = assert_raises Mechanize::ResponseCodeError do
@agent.response_read res, @req, @uri
end
assert_equal res, e.page
end
def test_response_redirect
@agent.redirect_ok = true
referer = page 'http://example/referer'
page = fake_page
page = @agent.response_redirect({ 'Location' => '/index.html' }, :get,
page, 0, {}, referer)
assert_equal URI('http://fake.example/index.html'), page.uri
assert_equal 'http://example/referer', requests.first['Referer']
end
def test_response_redirect_header
@agent.redirect_ok = true
referer = page 'http://example/referer'
headers = {
'Range' => 'bytes=0-9999',
'Content-Type' => 'application/x-www-form-urlencoded',
'CONTENT-LENGTH' => '9999',
'content-md5' => '14758f1afd44c09b7992073ccf00b43d',
}
page = fake_page
page = @agent.response_redirect({ 'Location' => '/http_headers' }, :get,
page, 0, headers, referer)
assert_equal URI('http://fake.example/http_headers'), page.uri
assert_match 'range|bytes=0-9999', page.body
refute_match 'content-type|application/x-www-form-urlencoded', page.body
refute_match 'content-length|9999', page.body
refute_match 'content-md5|14758f1afd44c09b7992073ccf00b43d', page.body
end
def test_response_redirect_malformed
@agent.redirect_ok = true
referer = page 'http://example/referer'
page = fake_page
page = @agent.response_redirect({ 'Location' => '/index.html?q=あ' }, :get,
page, 0, {}, referer)
assert_equal URI('http://fake.example/index.html?q=%E3%81%82'), page.uri
assert_equal 'http://example/referer', requests.first['Referer']
end
def test_response_redirect_insecure
@agent.redirect_ok = true
referer = page 'http://example/referer'
assert_raises Mechanize::Error do
@agent.response_redirect({ 'Location' => 'file:///etc/passwd' }, :get,
fake_page, 0, {}, referer)
end
end
def test_response_redirect_limit
@agent.redirect_ok = true
referer = page 'http://example/referer'
assert_raises Mechanize::RedirectLimitReachedError do
@agent.response_redirect({ 'Location' => '/index.html' }, :get,
fake_page, @agent.redirection_limit, {}, referer)
end
end
def test_response_redirect_to_cross_site_with_credential
@agent.redirect_ok = true
headers = {
'Range' => 'bytes=0-9999',
'AUTHORIZATION' => 'Basic xxx',
'cookie' => 'name=value',
}
page = html_page ''
page = @agent.response_redirect({ 'Location' => 'http://trap/http_headers' }, :get,
page, 0, headers)
refute_includes(headers.keys, "AUTHORIZATION")
refute_includes(headers.keys, "cookie")
assert_match("range|bytes=0-9999", page.body)
refute_match("authorization|Basic xxx", page.body)
refute_match("cookie|name=value", page.body)
end
def test_response_redirect_to_same_site_with_credential
@agent.redirect_ok = true
headers = {
'Range' => 'bytes=0-9999',
'AUTHORIZATION' => 'Basic xxx',
'cookie' => 'name=value',
}
page = html_page ''
page = @agent.response_redirect({ 'Location' => '/http_headers' }, :get,
page, 0, headers)
assert_includes(headers.keys, "AUTHORIZATION")
assert_includes(headers.keys, "cookie")
assert_match("range|bytes=0-9999", page.body)
assert_match("authorization|Basic xxx", page.body)
assert_match("cookie|name=value", page.body)
end
def test_response_redirect_to_same_site_diff_port_with_credential
@agent.redirect_ok = true
headers = {
'Range' => 'bytes=0-9999',
'AUTHORIZATION' => 'Basic xxx',
'cookie' => 'name=value',
}
page = html_page ''
page = @agent.response_redirect({ 'Location' => 'http://example:81/http_headers' }, :get,
page, 0, headers)
refute_includes(headers.keys, "AUTHORIZATION")
assert_includes(headers.keys, "cookie")
assert_match("range|bytes=0-9999", page.body)
refute_match("authorization|Basic xxx", page.body)
assert_match("cookie|name=value", page.body)
end
def test_response_redirect_not_ok
@agent.redirect_ok = false
page = fake_page
page = @agent.response_redirect({ 'Location' => '/other' }, :get, page, 0,
{}, page)
assert_equal URI('http://fake.example'), page.uri
end
def test_response_redirect_permanent
@agent.redirect_ok = :permanent
response = Net::HTTPMovedPermanently.allocate
response.instance_variable_set :@header, { 'location' => %w[/index.html] }
page = fake_page
page = @agent.response_redirect response, :get, page, 0, {}, page
assert_equal URI('http://fake.example/index.html'), page.uri
end
def test_response_redirect_permanent_temporary
@agent.redirect_ok = :permanent
response = Net::HTTPMovedTemporarily.allocate
response.instance_variable_set :@header, { 'location' => %w[/index.html] }
page = fake_page
page = @agent.response_redirect response, :get, page, 0, {}, page
assert_equal URI('http://fake.example/'), page.uri
end
def test_retry_change_request_equals
unless Gem::Requirement.new("< 4.0.0").satisfied_by?(Gem::Version.new(Net::HTTP::Persistent::VERSION))
# see https://github.com/drbrain/net-http-persistent/pull/100
skip("net-http-persistent 4.0.0 and later does not support retry_change_requests")
end
refute @agent.http.retry_change_requests
@agent.retry_change_requests = true
assert @agent.http.retry_change_requests
end
def test_robots_allowed_eh
allowed = URI 'http://localhost/index.html'
disallowed = URI 'http://localhost/norobots.html'
assert @agent.robots_allowed? allowed
refute @agent.robots_allowed? disallowed
refute @agent.robots_disallowed? allowed
assert @agent.robots_disallowed? disallowed
end
def test_robots_allowed_eh_noindex
@agent.robots = true
noindex = URI 'http://localhost/noindex.html'
assert @agent.robots_allowed? noindex
assert_raises Mechanize::RobotsDisallowedError do
@agent.fetch noindex
end
end
def test_robots_infinite_loop
@agent.robots = true
@agent.redirect_ok = true
assert_raises Mechanize::RobotsDisallowedError do
@agent.fetch URI('http://301/norobots.html')
end
@agent.fetch URI('http://301/robots.html')
end
def test_set_proxy
@agent.set_proxy 'www.example.com', 9001, 'joe', 'lol'
assert_equal @agent.proxy_uri.host, 'www.example.com'
assert_equal @agent.proxy_uri.port, 9001
assert_equal @agent.proxy_uri.user, 'joe'
assert_equal @agent.proxy_uri.password, 'lol'
end
def test_set_proxy_port_string
@agent.set_proxy 'www.example.com', '9001', 'joe', 'lol'
assert_equal @agent.proxy_uri.host, 'www.example.com'
assert_equal @agent.proxy_uri.port, 9001
assert_equal @agent.proxy_uri.user, 'joe'
assert_equal @agent.proxy_uri.password, 'lol'
end
def test_set_proxy_service_name
@agent.set_proxy 'www.example.com', 'http', 'joe', 'lol'
assert_equal @agent.proxy_uri.host, 'www.example.com'
assert_equal @agent.proxy_uri.port, 80
assert_equal @agent.proxy_uri.user, 'joe'
assert_equal @agent.proxy_uri.password, 'lol'
end
def test_set_proxy_service_name_bad
e = assert_raises ArgumentError do
@agent.set_proxy 'www.example.com', 'nonexistent service', 'joe', 'lol'
end
assert_equal 'invalid value for port: "nonexistent service"', e.message
end
def test_set_proxy_with_scheme
@agent.set_proxy 'http://www.example.com', 9001, 'joe', 'lol'
assert_equal @agent.proxy_uri.host, 'www.example.com'
assert_equal @agent.proxy_uri.port, 9001
assert_equal @agent.proxy_uri.user, 'joe'
assert_equal @agent.proxy_uri.password, 'lol'
end
def test_set_proxy_url
@agent.set_proxy 'http://joe:lol@www.example.com:9001'
assert_equal @agent.proxy_uri.host, 'www.example.com'
assert_equal @agent.proxy_uri.port, 9001
assert_equal @agent.proxy_uri.user, 'joe'
assert_equal @agent.proxy_uri.password, 'lol'
end
def test_set_proxy_uri
@agent.set_proxy URI('http://joe:lol@www.example.com:9001')
assert_equal @agent.proxy_uri.host, 'www.example.com'
assert_equal @agent.proxy_uri.port, 9001
assert_equal @agent.proxy_uri.user, 'joe'
assert_equal @agent.proxy_uri.password, 'lol'
end
def test_set_proxy_url_and_credentials
@agent.set_proxy 'http://www.example.com:9001', nil, 'joe', 'lol'
assert_equal @agent.proxy_uri.host, 'www.example.com'
assert_equal @agent.proxy_uri.port, 9001
assert_equal @agent.proxy_uri.user, 'joe'
assert_equal @agent.proxy_uri.password, 'lol'
end
def test_setting_agent_name
mech = Mechanize.new 'user-set-name'
assert_equal 'user-set-name', mech.agent.http.name
end
def test_ssl
in_tmpdir do
store = OpenSSL::X509::Store.new
@agent.ca_file = '.'
@agent.cert_store = store
@agent.certificate = ssl_certificate
@agent.private_key = ssl_private_key
@agent.ssl_version = 'SSLv3'
@agent.verify_callback = proc { |ok, context| }
http = @agent.http
assert_equal '.', http.ca_file
assert_equal store, http.cert_store
assert_equal ssl_certificate, http.certificate
assert_equal ssl_private_key, http.private_key
assert_equal 'SSLv3', http.ssl_version
assert_equal OpenSSL::SSL::VERIFY_PEER, http.verify_mode
assert http.verify_callback
end
end
def test_use_tempfile_eh
refute @agent.use_tempfile? nil
@agent.max_file_buffer = 1
refute @agent.use_tempfile? 0
assert @agent.use_tempfile? 1
@agent.max_file_buffer = nil
refute @agent.use_tempfile? 1
end
def test_verify_none_equals
@agent.verify_mode = OpenSSL::SSL::VERIFY_NONE
http = @agent.http
assert_equal OpenSSL::SSL::VERIFY_NONE, http.verify_mode
end
end
mechanize-2.10.1/test/test_mechanize_http_auth_store.rb 0000644 0000041 0000041 00000012535 14645745627 023422 0 ustar www-data www-data # coding: utf-8
# frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeHttpAuthStore < Mechanize::TestCase
def setup
super
@store = Mechanize::HTTP::AuthStore.new
@uri = URI.parse 'http://example/'
end
def test_add_auth
@store.add_auth @uri + '/path', 'user', 'pass'
expected = {
@uri => {
nil => ['user', 'pass', nil],
}
}
assert_equal expected, @store.auth_accounts
end
def test_add_auth_domain
@store.add_auth @uri + '/path', 'user1', 'pass', nil, 'domain'
expected = {
@uri => {
nil => %w[user1 pass domain],
}
}
assert_equal expected, @store.auth_accounts
e = assert_raises ArgumentError do
@store.add_auth @uri, 'user3', 'pass', 'realm', 'domain'
end
assert_equal 'NTLM domain given with realm which NTLM does not use',
e.message
end
def test_add_auth_realm
@store.add_auth @uri, 'user1', 'pass'
@store.add_auth @uri, 'user2', 'pass', 'realm'
expected = {
@uri => {
nil => ['user1', 'pass', nil],
'realm' => ['user2', 'pass', nil],
}
}
assert_equal expected, @store.auth_accounts
end
def test_add_auth_realm_case
@store.add_auth @uri, 'user1', 'pass', 'realm'
@store.add_auth @uri, 'user2', 'pass', 'Realm'
expected = {
@uri => {
'realm' => ['user1', 'pass', nil],
'Realm' => ['user2', 'pass', nil],
}
}
assert_equal expected, @store.auth_accounts
end
def test_add_auth_string
@store.add_auth "#{@uri}/path", 'user', 'pass'
expected = {
@uri => {
nil => ['user', 'pass', nil],
}
}
assert_equal expected, @store.auth_accounts
end
def test_add_default_auth
_, err = capture_io do
@store.add_default_auth 'user', 'pass'
end
expected = ['user', 'pass', nil]
assert_equal expected, @store.default_auth
assert_match 'DISCLOSURE WITHOUT YOUR KNOWLEDGE', err
capture_io do
@store.add_default_auth 'user', 'pass', 'realm'
end
expected = %w[user pass realm]
assert_equal expected, @store.default_auth
end
def test_credentials_eh
challenges = [
Mechanize::HTTP::AuthChallenge.new('Basic', 'realm' => 'r'),
Mechanize::HTTP::AuthChallenge.new('Digest', 'realm' => 'r'),
]
refute @store.credentials? @uri, challenges
@store.add_auth @uri, 'user', 'pass'
assert @store.credentials? @uri, challenges
assert @store.credentials? "#{@uri}/path", challenges
end
def test_credentials_for
assert_nil @store.credentials_for(@uri, 'realm')
@store.add_auth @uri, 'user', 'pass', 'realm'
assert_equal ['user', 'pass', nil], @store.credentials_for(@uri, 'realm')
assert_equal ['user', 'pass', nil],
@store.credentials_for(@uri.to_s, 'realm')
assert_nil @store.credentials_for(@uri, 'other')
end
def test_credentials_for_default
assert_nil @store.credentials_for(@uri, 'realm')
capture_io do
@store.add_default_auth 'user1', 'pass'
end
assert_equal ['user1', 'pass', nil], @store.credentials_for(@uri, 'realm')
@store.add_auth @uri, 'user2', 'pass'
assert_equal ['user2', 'pass', nil], @store.credentials_for(@uri, 'realm')
assert_equal ['user2', 'pass', nil], @store.credentials_for(@uri, 'other')
end
def test_credentials_for_no_realm
@store.add_auth @uri, 'user', 'pass' # no realm set
assert_equal ['user', 'pass', nil], @store.credentials_for(@uri, 'realm')
end
def test_credentials_for_realm
@store.add_auth @uri, 'user1', 'pass'
@store.add_auth @uri, 'user2', 'pass', 'realm'
assert_equal ['user2', 'pass', nil], @store.credentials_for(@uri, 'realm')
assert_equal ['user1', 'pass', nil], @store.credentials_for(@uri, 'other')
end
def test_credentials_for_realm_case
@store.add_auth @uri, 'user1', 'pass', 'realm'
@store.add_auth @uri, 'user2', 'pass', 'Realm'
assert_equal ['user1', 'pass', nil], @store.credentials_for(@uri, 'realm')
assert_equal ['user2', 'pass', nil], @store.credentials_for(@uri, 'Realm')
end
def test_credentials_for_path
@store.add_auth @uri, 'user', 'pass', 'realm'
uri = @uri + '/path'
assert_equal ['user', 'pass', nil], @store.credentials_for(uri, 'realm')
end
def test_remove_auth
@store.remove_auth @uri
assert_empty @store.auth_accounts
end
def test_remove_auth_both
@store.add_auth @uri, 'user1', 'pass'
@store.add_auth @uri, 'user2', 'pass', 'realm'
uri = @uri + '/path'
@store.remove_auth uri
assert_empty @store.auth_accounts
end
def test_remove_auth_realm
@store.add_auth @uri, 'user1', 'pass'
@store.add_auth @uri, 'user2', 'pass', 'realm'
@store.remove_auth @uri, 'realm'
expected = {
@uri => {
nil => ['user1', 'pass', nil]
}
}
assert_equal expected, @store.auth_accounts
end
def test_remove_auth_realm_case
@store.add_auth @uri, 'user1', 'pass', 'realm'
@store.add_auth @uri, 'user2', 'pass', 'Realm'
@store.remove_auth @uri, 'Realm'
expected = {
@uri => {
'realm' => ['user1', 'pass', nil]
}
}
assert_equal expected, @store.auth_accounts
end
def test_remove_auth_string
@store.add_auth @uri, 'user1', 'pass'
@store.remove_auth "#{@uri}/path"
assert_empty @store.auth_accounts
end
end
mechanize-2.10.1/test/test_mechanize_page_encoding.rb 0000644 0000041 0000041 00000014270 14645745627 022766 0 ustar www-data www-data # -*- coding: utf-8 -*-
# frozen_string_literal: true
require 'mechanize/test_case'
# tests for Page encoding and charset and parsing
class TestMechanizePageEncoding < Mechanize::TestCase
MECH_ASCII_ENCODING = 'US-ASCII'
def setup
super
@uri = URI('http://localhost/')
@response_headers = { 'content-type' => 'text/html' }
@body = +'hi'
end
def util_page body = @body, headers = @response_headers
Mechanize::Page.new @uri, headers, body && body.force_encoding(Encoding::BINARY), 200, @mech
end
def test_page_charset
charset = Mechanize::Page.charset 'text/html;charset=vAlue'
assert_equal 'vAlue', charset
charset = Mechanize::Page.charset 'text/html;charset=vaLue, text/html'
assert_equal 'vaLue', charset
charset = Mechanize::Page.charset 'text/html ; charset = valUe, text/html'
assert_equal 'valUe', charset
end
def test_page_charset_upcase
charset = Mechanize::Page.charset 'TEXT/HTML;CHARSET=UTF-8'
assert_equal 'UTF-8', charset
end
def test_page_charset_semicolon
charset = Mechanize::Page.charset 'text/html;charset=UTF-8;'
assert_equal 'UTF-8', charset
end
def test_page_charset_no_chaset_token
charset = Mechanize::Page.charset 'text/html'
assert_nil charset
end
def test_page_charset_returns_nil_when_charset_says_none
charset = Mechanize::Page.charset 'text/html;charset=none'
assert_nil charset
end
def test_page_charset_multiple
charset = Mechanize::Page.charset 'text/html;charset=111;charset=222'
assert_equal '111', charset
end
def test_page_response_header_charset
headers = { 'content-type' => 'text/html;charset=HEADER' }
charsets = Mechanize::Page.response_header_charset(headers)
assert_equal ['HEADER'], charsets
end
def test_page_response_header_charset_no_token
headers = {'content-type' => 'text/html'}
charsets = Mechanize::Page.response_header_charset(headers)
assert_equal [], charsets
headers = {'X-My-Header' => 'hello'}
charsets = Mechanize::Page.response_header_charset(headers)
assert_equal [], charsets
end
def test_page_response_header_charset_wrong_header
headers = { 'x-content-type' => 'text/html;charset=bogus' }
charsets = Mechanize::Page.response_header_charset(headers)
assert_equal [], charsets
end
def test_response_header_charset
page = util_page nil, {'content-type' => 'text/html;charset=HEADER'}
assert_equal ['HEADER'], page.response_header_charset
end
def test_page_meta_charset
body = ''
charsets = Mechanize::Page.meta_charset(body)
assert_equal ['META'], charsets
end
def test_page_meta_charset_is_empty_when_no_charset_meta
body = ''
charsets = Mechanize::Page.meta_charset(body)
assert_equal [], charsets
end
def test_page_meta_charset_no_content
body = ''
charsets = Mechanize::Page.meta_charset(body)
assert_empty charsets
end
# Test to fix issue: https://github.com/sparklemotion/mechanize/issues/143
def test_page_meta_charset_handles_whitespace
body = ''
charsets = Mechanize::Page.meta_charset(body)
assert_equal ["iso-8859-1"], charsets
end
def test_meta_charset
body = +''
page = util_page body
assert_equal ['META'], page.meta_charset
end
def test_detected_encoding
page = util_page
assert_equal MECH_ASCII_ENCODING, page.detected_encoding
end
def test_encodings
response = {'content-type' => 'text/html;charset=HEADER'}
body = +''
@mech.default_encoding = 'DEFAULT'
page = util_page body, response
assert_equal true, page.encodings.include?('HEADER')
assert_equal true, page.encodings.include?('META')
assert_equal true, page.encodings.include?(MECH_ASCII_ENCODING)
assert_equal true, page.encodings.include?('DEFAULT')
end
def test_parser_with_default_encoding
# pre test
assert_equal false, util_page.encodings.include?('Windows-1252')
@mech.default_encoding = 'Windows-1252'
page = util_page
assert_equal true, page.encodings.include?('Windows-1252')
end
def test_parser_force_default_encoding
@mech.default_encoding = 'Windows-1252'
@mech.force_default_encoding = true
page = util_page
assert page.encodings.include? 'Windows-1252'
end
def test_parser_encoding_equals_overwrites_force_default_encoding
@mech.default_encoding = 'Windows-1252'
@mech.force_default_encoding = true
page = util_page
assert_equal 'Windows-1252', page.encoding
page.encoding = 'ISO-8859-2'
assert_equal 'ISO-8859-2', page.encoding
end
def test_parser_encoding_when_searching_elements
skip "Encoding not implemented" unless have_encoding?
body = +'hi'
page = util_page body, 'content-type' => 'text/html,charset=ISO-8859-1'
result = page.search('#latin1')
assert_equal Encoding::UTF_8, result.text.encoding
end
def test_parser_error_message_containing_encoding_errors
skip if RUBY_ENGINE == 'jruby' # this is a libxml2-specific condition
# https://github.com/sparklemotion/mechanize/issues/553
body = +<<~EOF
EOF
page = util_page body
# this should not raise an "invalid byte sequence in UTF-8" error while processing parsing errors
page.search("body")
# let's assert on the setup: a libxml2-returned parsing error itself contains an invalid character
# note that this problem only appears in libxml <= 2.9.10
error = page.parser.errors.find { |e| e.message.include?("Comment not terminated") }
if error
exception = assert_raises(ArgumentError) do
error.message =~ /any regex just to trigger encoding error/
end
assert_includes(exception.message, "invalid byte sequence in UTF-8")
end
end
end
mechanize-2.10.1/test/test_mechanize_directory_saver.rb 0000644 0000041 0000041 00000002456 14645745627 023413 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeDirectorySaver < Mechanize::TestCase
def setup
super
@uri = URI 'http://example/relative/tc_relative_links.html'
@io = StringIO.new 'hello world'
end
def test_self_save_to
in_tmpdir do
saver = Mechanize::DirectorySaver.save_to 'dir'
saver.new @uri, nil, @io, 200
assert File.exist? 'dir/tc_relative_links.html'
refute File.exist? 'dir/relative'
end
end
def test_self_save_to_cd
in_tmpdir do
saver = Mechanize::DirectorySaver.save_to 'dir'
FileUtils.mkdir 'other'
Dir.chdir 'other' do
saver.new @uri, nil, @io, 200
end
assert File.exist? 'dir/tc_relative_links.html'
refute File.exist? 'dir/relative'
end
end
def test_with_decode_filename
in_tmpdir do
saver = Mechanize::DirectorySaver.save_to 'dir', :decode_filename => true
uri = URI 'http://example.com/foo+bar.html'
saver.new uri, nil, @io, 200
assert File.exist? 'dir/foo bar.html'
end
end
def test_initialize_no_save_dir
in_tmpdir do
e = assert_raises Mechanize::Error do
Mechanize::DirectorySaver.new @uri, nil, @io, 200
end
assert_match %r%no save directory specified%, e.message
end
end
end
mechanize-2.10.1/test/test_mechanize_form_radio_button.rb 0000644 0000041 0000041 00000003742 14645745627 023722 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeFormRadioButton < Mechanize::TestCase
def setup
super
@page = html_page <<-BODY
BODY
@form = @page.forms.first
@blue = @form.radiobutton_with :value => 'blue'
@brown = @form.radiobutton_with :value => 'brown'
@green = @form.radiobutton_with :value => 'green'
@red = @form.radiobutton_with :value => 'red'
@yellow = @form.radiobutton_with :id => 'a'
end
def test_check
@blue.check
assert @blue.checked?
refute @brown.checked?
refute @green.checked?
refute @red.checked?
refute @yellow.checked?
end
def test_check_multiple
@blue.check
@brown.check
refute @blue.checked?
assert @brown.checked?
refute @green.checked?
refute @red.checked?
refute @yellow.checked?
end
def test_click
@blue.click
assert @blue.checked?
@blue.click
refute @blue.checked?
end
def test_equals2
refute_equal @yellow, @red
other_yellow = @form.radiobutton_with :id => 'b'
assert_equal @yellow, other_yellow
end
def test_hash
refute_equal @yellow.hash, @red.hash
other_yellow = @form.radiobutton_with :id => 'b'
assert_equal @yellow.hash, other_yellow.hash
end
def test_label
assert_equal 'Blue', @blue.label.text
end
def test_uncheck
@blue.check
@blue.uncheck
refute @blue.checked?
refute @brown.checked?
refute @green.checked?
refute @red.checked?
refute @yellow.checked?
end
end
mechanize-2.10.1/test/test_mechanize_form_keygen.rb 0000644 0000041 0000041 00000001664 14645745627 022514 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeFormKeygen < Mechanize::TestCase
def setup
super
keygen = node('keygen',
'name' => 'userkey',
'challenge' => 'f4832e1d200df3df8c5c859edcabe52f')
@keygen = Mechanize::Form::Keygen.new keygen
end
def test_challenge
assert_equal "f4832e1d200df3df8c5c859edcabe52f", @keygen.challenge
end
def test_key
assert @keygen.key.kind_of?(OpenSSL::PKey::PKey), "Not an OpenSSL key"
assert @keygen.key.private?, "Not a private key"
end
def test_spki_signature
skip("JRuby PKI doesn't handle this for reasons I've been unable to understand") if RUBY_ENGINE=~/jruby/
spki = OpenSSL::Netscape::SPKI.new @keygen.value
assert_equal @keygen.challenge, spki.challenge
assert_equal @keygen.key.public_key.to_pem, spki.public_key.to_pem
assert spki.verify(@keygen.key.public_key)
end
end
mechanize-2.10.1/test/test_mechanize_file.rb 0000644 0000041 0000041 00000005403 14645745627 021121 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeFile < Mechanize::TestCase
def setup
super
@parser = Mechanize::File
end
def test_save
uri = URI 'http://example/name.html'
page = Mechanize::File.new uri, nil, '0123456789'
Dir.mktmpdir do |dir|
Dir.chdir dir do
filename = page.save 'test.html'
assert File.exist? 'test.html'
assert_equal '0123456789', File.read('test.html')
assert_equal "test.html", filename
filename = page.save 'test.html'
assert File.exist? 'test.html.1'
assert_equal '0123456789', File.read('test.html.1')
assert_equal "test.html.1", filename
filename = page.save 'test.html'
assert File.exist? 'test.html.2'
assert_equal '0123456789', File.read('test.html.2')
assert_equal "test.html.2", filename
end
end
end
def test_save_default
uri = URI 'http://example/test.html'
page = Mechanize::File.new uri, nil, ''
Dir.mktmpdir do |dir|
Dir.chdir dir do
filename = page.save
assert File.exist? 'test.html'
assert_equal "test.html", filename
filename = page.save
assert File.exist? 'test.html.1'
assert_equal "test.html.1", filename
filename = page.save
assert File.exist? 'test.html.2'
assert_equal "test.html.2", filename
end
end
end
def test_save_default_dots
uri = URI 'http://localhost/../test.html'
page = Mechanize::File.new uri, nil, ''
Dir.mktmpdir do |dir|
Dir.chdir dir do
filename = page.save
assert File.exist? 'test.html'
assert_equal "test.html", filename
filename = page.save
assert File.exist? 'test.html.1'
assert_equal "test.html.1", filename
end
end
end
def test_filename
uri = URI 'http://localhost/test.html'
page = Mechanize::File.new uri, nil, ''
assert_equal "test.html", page.filename
end
def test_save_overwrite
uri = URI 'http://example/test.html'
page = Mechanize::File.new uri, nil, ''
Dir.mktmpdir do |dir|
Dir.chdir dir do
filename = page.save 'test.html'
assert File.exist? 'test.html'
assert_equal "test.html", filename
filename = page.save! 'test.html'
assert File.exist? 'test.html'
refute File.exist? 'test.html.1'
assert_equal "test.html", filename
end
end
end
def test_save_bang_does_not_allow_command_injection
skip if windows?
uri = URI 'http://example/test.html'
page = Mechanize::File.new uri, nil, ''
in_tmpdir do
page.save!('| ruby -rfileutils -e \'FileUtils.touch("vul.txt")\'')
refute_operator(File, :exist?, "vul.txt")
end
end
end
mechanize-2.10.1/test/test_mechanize_form_field.rb 0000644 0000041 0000041 00000003353 14645745627 022312 0 ustar www-data www-data # frozen_string_literal: true
require 'mechanize/test_case'
class TestMechanizeFormField < Mechanize::TestCase
def test_inspect
field = node 'input'
field = Mechanize::Form::Field.new field, 'a&b'
assert_match "value: a&b", field.inspect
end
def test_name
field = node 'input', 'name' => 'a&b'
field = Mechanize::Form::Field.new field
assert_equal 'a&b', field.name
end
def test_name_entity
field = node 'input', 'name' => 'a&b'
field = Mechanize::Form::Field.new field
assert_equal 'a&b', field.name
end
def test_name_entity_numeric
field = node 'input', 'name' => 'a&b'
field = Mechanize::Form::Field.new field
assert_equal 'a&b', field.name
end
def test_spaceship
doc = Nokogiri::HTML::Document.new
node = doc.create_element('input')
node['name'] = 'foo'
node['value'] = 'bar'
a = Mechanize::Form::Field.new(node)
b = Mechanize::Form::Field.new({'name' => 'foo'}, 'bar')
c = Mechanize::Form::Field.new({'name' => 'foo'}, 'bar')
assert_equal [a, b], [a, b].sort
assert_equal [a, b], [b, a].sort
assert_equal [b, c].sort, [b, c].sort
end
def test_value
field = node 'input'
field = Mechanize::Form::Field.new field, 'a&b'
assert_equal 'a&b', field.value
end
def test_value_entity
field = node 'input'
field = Mechanize::Form::Field.new field, 'a&b'
assert_equal 'a&b', field.value
end
def test_value_entity_numeric
field = node 'input'
field = Mechanize::Form::Field.new field, 'a&b'
assert_equal 'a&b', field.value
end
def test_raw_value
field = node 'input'
field = Mechanize::Form::Field.new field, 'a&b'
assert_equal 'a&b', field.raw_value
end
end
mechanize-2.10.1/test/htdocs/ 0000755 0000041 0000041 00000000000 14645745627 016055 5 ustar www-data www-data mechanize-2.10.1/test/htdocs/tc_charset.html 0000644 0000041 0000041 00000000170 14645745627 021060 0 ustar www-data www-data
mechanize-2.10.1/test/htdocs/button.jpg 0000644 0000041 0000041 00000001727 14645745627 020101 0 ustar www-data www-data JFIF H H Exif MM * Created with The GIMP C !"$"$ C d" ( !1A"Q2B ? x0<ثg(FJT 9ˊgZq. qXO' tf
l+uhQi^(@$ /\3ҵ>EKcUm1dc%/-J%-H`j7v
̀ks ){C9[[z^\^dqK
JAJUJ (D;KvŮZ[T,{_a}(
#M:3XIAQfMA:GI duiimn^eq's^\!**~O99oPȿRH=.;]}8➻gQ6u
VŠl1XmqN1TS#KZVg=fiP R3Eʿ@!U]ڵk+JyZd>hӛu6ze)Q!'>ޗmS}2>RqM;O]]zTu4ս^UiutzsK2tS !WG2zt!
Be3:U
QQ$$vp:|ccccccccc mechanize-2.10.1/test/htdocs/meta_cookie.html 0000644 0000041 0000041 00000000601 14645745627 021217 0 ustar www-data www-data
no image
mechanize-2.10.1/test/htdocs/index.html 0000644 0000041 0000041 00000000145 14645745627 020052 0 ustar www-data www-data
Page Title
mechanize-2.10.1/test/htdocs/canonical_uri.html 0000644 0000041 0000041 00000000264 14645745627 021553 0 ustar www-data www-data
test
mechanize-2.10.1/test/htdocs/tc_follow_meta.html 0000644 0000041 0000041 00000000244 14645745627 021741 0 ustar www-data www-data
This page has a meta refresh.
mechanize-2.10.1/test/htdocs/link with space.html 0000644 0000041 0000041 00000000132 14645745627 021704 0 ustar www-data www-data
This is a webpage that has a space in the filename.
mechanize-2.10.1/test/htdocs/tc_form_action.html 0000644 0000041 0000041 00000002417 14645745627 021735 0 ustar www-data www-data
Page Title
mechanize-2.10.1/test/htdocs/tc_base_link.html 0000644 0000041 0000041 00000000173 14645745627 021361 0 ustar www-data www-data
test
mechanize-2.10.1/test/htdocs/frame_referer_test.html 0000644 0000041 0000041 00000000377 14645745627 022615 0 ustar www-data www-data
A simple frameset document
mechanize-2.10.1/test/htdocs/form_multi_select.html 0000644 0000041 0000041 00000000760 14645745627 022462 0 ustar www-data www-data
mechanize-2.10.1/test/htdocs/rails_3_encoding_hack_form_test.html 0000644 0000041 0000041 00000002102 14645745627 025210 0 ustar www-data www-data
mechanize-2.10.1/test/htdocs/tc_links.html 0000644 0000041 0000041 00000001473 14645745627 020556 0 ustar www-data www-data
Bold DudeDudeAaron James PattersonAaron PattersonThing!Ruby Rocks!encoded spacenot encoded spaceunusual charactersempty hrefjavascript link
mechanize-2.10.1/test/htdocs/tc_meta_in_body.html 0000644 0000041 0000041 00000000245 14645745627 022063 0 ustar www-data www-data
This page has a meta refresh.
mechanize-2.10.1/test/htdocs/unusual______.html 0000644 0000041 0000041 00000000126 14645745627 021530 0 ustar www-data www-data
This is a webpage that has a very unusual name.
mechanize-2.10.1/test/htdocs/tc_textarea.html 0000644 0000041 0000041 00000001241 14645745627 021244 0 ustar www-data www-data
tc_textarea.html
mechanize-2.10.1/test/htdocs/test_click.html 0000644 0000041 0000041 00000000521 14645745627 021065 0 ustar www-data www-data
Page TitleThis link is not called "A Link"A Link