mechanize-2.10.1/0000755000004100000410000000000014645745627013612 5ustar www-datawww-datamechanize-2.10.1/.autotest0000644000004100000410000000015114645745627015460 0ustar www-datawww-datarequire 'autotest/restart' Autotest.add_hook :initialize do |at| at.testlib = 'minitest/autorun' end mechanize-2.10.1/.gitignore0000644000004100000410000000016414645745627015603 0ustar www-datawww-data*.swp *.gem *.rbc /.bundle /.config /Gemfile.lock /TAGS /pkg /tags /.rvmrc digest.htpasswd doc rdoc .yardoc _yardoc mechanize-2.10.1/.github/0000755000004100000410000000000014645745627015152 5ustar www-datawww-datamechanize-2.10.1/.github/dependabot.yml0000644000004100000410000000066214645745627020006 0ustar www-datawww-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/0000755000004100000410000000000014645745627017207 5ustar www-datawww-datamechanize-2.10.1/.github/workflows/upstream.yml0000644000004100000410000000233114645745627021571 0ustar www-datawww-dataname: "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.yml0000644000004100000410000000166414645745627020334 0ustar www-datawww-dataname: "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/0000755000004100000410000000000014645745627014360 5ustar www-datawww-datamechanize-2.10.1/lib/mechanize.rb0000644000004100000410000010615014645745627016653 0ustar www-datawww-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/0000755000004100000410000000000014645745627016323 5ustar www-datawww-datamechanize-2.10.1/lib/mechanize/redirect_limit_reached_error.rb0000644000004100000410000000061714645745627024537 0ustar www-datawww-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.rb0000644000004100000410000004461414645745627017624 0ustar www-datawww-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.rb0000644000004100000410000000262114645745627020122 0ustar www-datawww-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.rb0000644000004100000410000000453414645745627021137 0ustar www-datawww-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.rb0000644000004100000410000000030714645745627023775 0ustar www-datawww-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/0000755000004100000410000000000014645745627020275 5ustar www-datawww-datamechanize-2.10.1/lib/mechanize/test_case/referer_servlet.rb0000644000004100000410000000046014645745627024020 0ustar www-datawww-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.rb0000644000004100000410000000215414645745627025050 0ustar www-datawww-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.rb0000644000004100000410000000036214645745627025102 0ustar www-datawww-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.rb0000644000004100000410000000533314645745627022475 0ustar www-datawww-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.rb0000644000004100000410000000041014645745627023317 0ustar www-datawww-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.rb0000644000004100000410000000142414645745627024667 0ustar www-datawww-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.rb0000644000004100000410000000057514645745627024604 0ustar www-datawww-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.rb0000644000004100000410000000053114645745627025062 0ustar www-datawww-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.rb0000644000004100000410000000171314645745627022132 0ustar www-datawww-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.rb0000644000004100000410000000147014645745627023341 0ustar www-datawww-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/.document0000644000004100000410000000004014645745627022106 0ustar www-datawww-data# Don't document this directory mechanize-2.10.1/lib/mechanize/test_case/infinite_refresh_servlet.rb0000644000004100000410000000066014645745627025713 0ustar www-datawww-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.rb0000644000004100000410000000042614645745627023620 0ustar www-datawww-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.rb0000644000004100000410000000055414645745627026420 0ustar www-datawww-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.rb0000644000004100000410000000053314645745627024501 0ustar www-datawww-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.rb0000644000004100000410000000176014645745627023335 0ustar www-datawww-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 << "
  • #{key}:#{value}" } } res.body = <<-BODY GET results
      #{query.join "\n"}
    #{req.query}
    BODY end def do_POST(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 << "
  • #{key}:#{value}" } } res.body = <<-BODY POST results
      #{query.join "\n"}
    #{req.body}
    BODY end end mechanize-2.10.1/lib/mechanize/test_case/ntlm_servlet.rb0000644000004100000410000000157714645745627023352 0ustar www-datawww-data# frozen_string_literal: true class NTLMServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) if req['Authorization'] =~ /^NTLM (.*)/ then authorization = $1.unpack('m*').first if authorization =~ /^NTLMSSP\000\001/ then type_2 = 'TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mr' \ 'ze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4A' \ 'AgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUA' \ 'UgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIA' \ 'cwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMA' \ 'bwBtAAAAAAA=' res['WWW-Authenticate'] = "NTLM #{type_2}" res.status = 401 elsif authorization =~ /^NTLMSSP\000\003/ then res.body = 'ok' else res['WWW-Authenticate'] = 'NTLM' res.status = 401 end else res['WWW-Authenticate'] = 'NTLM' res.status = 401 end end end mechanize-2.10.1/lib/mechanize/test_case/refresh_without_url.rb0000644000004100000410000000054114645745627024725 0ustar www-datawww-data# frozen_string_literal: true class RefreshWithoutUrl < WEBrick::HTTPServlet::AbstractServlet @@count = 0 def do_GET(req, res) address = "#{req.host}:#{req.port}" res['Content-Type'] = "text/html" @@count += 1 if @@count > 1 res['Refresh'] = "0; url=http://#{address}/"; else res['Refresh'] = "0"; end end end mechanize-2.10.1/lib/mechanize/test_case/refresh_with_empty_url.rb0000644000004100000410000000054714645745627025421 0ustar www-datawww-data# frozen_string_literal: true class RefreshWithEmptyUrl < WEBrick::HTTPServlet::AbstractServlet @@count = 0 def do_GET(req, res) address = "#{req.host}:#{req.port}" res.content_type = "text/html" @@count += 1 if @@count > 1 res['Refresh'] = "0; url=http://#{address}/"; else res['Refresh'] = "0; url="; end end end mechanize-2.10.1/lib/mechanize/test_case/redirect_servlet.rb0000644000004100000410000000062414645745627024171 0ustar www-datawww-data# frozen_string_literal: true class RedirectServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) res['Content-Type'] = req.query['ct'] || 'text/html' res.status = req.query['code'] ? req.query['code'].to_i : '302' res['Location'] = req['X-Location'] || '/verb' end alias :do_POST :do_GET alias :do_HEAD :do_GET alias :do_PUT :do_GET alias :do_DELETE :do_GET end mechanize-2.10.1/lib/mechanize/test_case/one_cookie_no_spaces_servlet.rb0000644000004100000410000000057014645745627026534 0ustar www-datawww-data# frozen_string_literal: true class OneCookieNoSpacesServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) cookie = WEBrick::Cookie.new("foo", "bar") cookie.path = "/" cookie.expires = Time.now + 86400 res.cookies << cookie.to_s.gsub(/; /, ';') res['Content-Type'] = "text/html" res.body = "hello" end end mechanize-2.10.1/lib/mechanize/test_case/basic_auth_servlet.rb0000644000004100000410000000116514645745627024473 0ustar www-datawww-data# frozen_string_literal: true class BasicAuthServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req,res) htpd = nil Tempfile.open 'dot.htpasswd' do |io| htpd = WEBrick::HTTPAuth::Htpasswd.new(io.path) htpd.set_passwd('Blah', 'user', 'pass') end authenticator = WEBrick::HTTPAuth::BasicAuth.new({ :UserDB => htpd, :Realm => 'Blah', :Logger => Logger.new(nil) }) 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/infinite_redirect_servlet.rb0000644000004100000410000000061514645745627026056 0ustar www-datawww-data# frozen_string_literal: true class InfiniteRedirectServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) 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['Location'] = "/infinite_redirect?q=#{number + 1}" end alias :do_POST :do_GET end mechanize-2.10.1/lib/mechanize/test_case/bad_chunking_servlet.rb0000644000004100000410000000043314645745627025002 0ustar www-datawww-data# frozen_string_literal: true class BadChunkingServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET req, res res.keep_alive = false if res.respond_to? :keep_alive= res['Transfer-Encoding'] = 'chunked' res.body = <<-BODY a\r 0123456789\r 0\r BODY end end mechanize-2.10.1/lib/mechanize/test_case/file_upload_servlet.rb0000644000004100000410000000066114645745627024654 0ustar www-datawww-data# frozen_string_literal: true class FileUploadServlet < WEBrick::HTTPServlet::AbstractServlet def do_POST req, res res.body = req.body end def do_GET req, res res.content_type = 'text/html' res.body = <<-BODY Fill in this form

    You can POST anything to this endpoint, though

    BODY end end mechanize-2.10.1/lib/mechanize/test_case/send_cookies_servlet.rb0000644000004100000410000000056314645745627025037 0ustar www-datawww-data# frozen_string_literal: true class SendCookiesServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) res.content_type = 'text/html' cookies = req.cookies.map do |c| "
  • #{c.name}:#{c.value}" end.join "\n" res.body = <<-BODY Your cookies
      #{cookies}
    BODY end end mechanize-2.10.1/lib/mechanize/test_case/modified_since_servlet.rb0000644000004100000410000000105014645745627025323 0ustar www-datawww-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.rb0000644000004100000410000000062014645745627025214 0ustar www-datawww-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.rb0000644000004100000410000000230514645745627027117 0ustar www-datawww-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.rb0000644000004100000410000000034214645745627017626 0ustar www-datawww-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.rb0000644000004100000410000000240414645745627020351 0ustar www-datawww-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.rb0000644000004100000410000000066214645745627023571 0ustar www-datawww-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.rb0000644000004100000410000000165614645745627022722 0ustar www-datawww-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.rb0000644000004100000410000001154414645745627022356 0ustar www-datawww-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.rb0000644000004100000410000000403314645745627022054 0ustar www-datawww-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.rb0000644000004100000410000000021614645745627017731 0ustar www-datawww-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.rb0000644000004100000410000000031614645745627024113 0ustar www-datawww-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.rb0000644000004100000410000001101714645745627017625 0ustar www-datawww-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.rb0000644000004100000410000000065014645745627020264 0ustar www-datawww-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.rb0000644000004100000410000001106114645745627020754 0ustar www-datawww-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.rb0000644000004100000410000000062514645745627022577 0ustar www-datawww-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/0000755000004100000410000000000014645745627017266 5ustar www-datawww-datamechanize-2.10.1/lib/mechanize/form/select_list.rb0000644000004100000410000000207714645745627022133 0ustar www-datawww-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.rb0000644000004100000410000000013014645745627020571 0ustar www-datawww-data# frozen_string_literal: true class Mechanize::Form::Text < Mechanize::Form::Field end mechanize-2.10.1/lib/mechanize/form/submit.rb0000644000004100000410000000013314645745627021113 0ustar www-datawww-data# frozen_string_literal: true class Mechanize::Form::Submit < Mechanize::Form::Button end mechanize-2.10.1/lib/mechanize/form/hidden.rb0000644000004100000410000000013214645745627021042 0ustar www-datawww-data# frozen_string_literal: true class Mechanize::Form::Hidden < Mechanize::Form::Field end mechanize-2.10.1/lib/mechanize/form/button.rb0000644000004100000410000000017214645745627021126 0ustar www-datawww-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.rb0000644000004100000410000000072514645745627021544 0ustar www-datawww-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.rb0000644000004100000410000000223714645745627021127 0ustar www-datawww-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.rb0000644000004100000410000000225514645745627022310 0ustar www-datawww-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.rb0000644000004100000410000000065214645745627022273 0ustar www-datawww-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.rb0000644000004100000410000000122614645745627022077 0ustar www-datawww-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.rb0000644000004100000410000000176614645745627021107 0ustar www-datawww-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.rb0000644000004100000410000000013214645745627020731 0ustar www-datawww-data# frozen_string_literal: true class Mechanize::Form::Reset < Mechanize::Form::Button end mechanize-2.10.1/lib/mechanize/form/multi_select_list.rb0000644000004100000410000000357414645745627023350 0ustar www-datawww-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.rb0000644000004100000410000000477714645745627020715 0ustar www-datawww-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.rb0000644000004100000410000000013414645745627021426 0ustar www-datawww-data# frozen_string_literal: true class Mechanize::Form::Textarea < Mechanize::Form::Field end mechanize-2.10.1/lib/mechanize/element_matcher.rb0000644000004100000410000000342314645745627022006 0ustar www-datawww-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/0000755000004100000410000000000014645745627017237 5ustar www-datawww-datamechanize-2.10.1/lib/mechanize/page/link.rb0000644000004100000410000000515414645745627020526 0ustar www-datawww-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 # 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.rb0000644000004100000410000000405414645745627022233 0ustar www-datawww-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.rb0000644000004100000410000000644314645745627020655 0ustar www-datawww-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.rb0000644000004100000410000000036414645745627020501 0ustar www-datawww-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.rb0000644000004100000410000000053614645745627020647 0ustar www-datawww-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.rb0000644000004100000410000000131414645745627020655 0ustar www-datawww-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.rb0000644000004100000410000000056414645745627021344 0ustar www-datawww-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.rb0000644000004100000410000001722414645745627020630 0ustar www-datawww-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.rb0000644000004100000410000000063214645745627022603 0ustar www-datawww-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.rb0000644000004100000410000000145014645745627022711 0ustar www-datawww-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.rb0000644000004100000410000000135414645745627023603 0ustar www-datawww-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.rb0000644000004100000410000000355714645745627020471 0ustar www-datawww-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.rb0000644000004100000410000003502714645745627017573 0ustar www-datawww-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.rb0000644000004100000410000000442214645745627017571 0ustar www-datawww-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.rb0000644000004100000410000000010714645745627020333 0ustar www-datawww-data# frozen_string_literal: true class Mechanize VERSION = "2.10.1" end mechanize-2.10.1/lib/mechanize/file_connection.rb0000644000004100000410000000066314645745627022013 0ustar www-datawww-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.rb0000644000004100000410000000110014645745627025052 0ustar www-datawww-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/0000755000004100000410000000000014645745627017302 5ustar www-datawww-datamechanize-2.10.1/lib/mechanize/http/auth_store.rb0000644000004100000410000000601614645745627022007 0ustar www-datawww-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.rb0000644000004100000410000000645714645745627024601 0ustar www-datawww-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.rb0000644000004100000410000010701514645745627020731 0ustar www-datawww-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.rb0000644000004100000410000000105614645745627021752 0ustar www-datawww-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.rb0000644000004100000410000001063214645745627025303 0ustar www-datawww-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.rb0000644000004100000410000000245114645745627022574 0ustar www-datawww-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.rb0000644000004100000410000000243114645745627020447 0ustar www-datawww-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 endmechanize-2.10.1/lib/mechanize/file_saver.rb0000644000004100000410000000155314645745627020773 0ustar www-datawww-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.rb0000644000004100000410000000660414645745627020152 0ustar www-datawww-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.rb0000644000004100000410000000263414645745627021512 0ustar www-datawww-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.rdoc0000644000004100000410000001326614645745627015330 0ustar www-datawww-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.txt0000644000004100000410000000225714645745627015443 0ustar www-datawww-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/0000755000004100000410000000000014645745627014571 5ustar www-datawww-datamechanize-2.10.1/test/test_mechanize_image.rb0000644000004100000410000000022014645745627021254 0ustar www-datawww-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.rb0000644000004100000410000000042614645745627026414 0ustar www-datawww-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/0000755000004100000410000000000014645745627015502 5ustar www-datawww-datamechanize-2.10.1/test/data/server.csr0000644000004100000410000000130414645745627017517 0ustar www-datawww-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/htpasswd0000644000004100000410000000002314645745627017255 0ustar www-datawww-datamech:44E/qORekFV0E mechanize-2.10.1/test/data/server.pem0000644000004100000410000000156714645745627017524 0ustar www-datawww-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.key0000644000004100000410000000156714645745627017533 0ustar www-datawww-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.crt0000644000004100000410000000170414645745627017524 0ustar www-datawww-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.rb0000644000004100000410000000056114645745627022321 0ustar www-datawww-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.rb0000644000004100000410000000060514645745627022020 0ustar www-datawww-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.rb0000644000004100000410000004072214645745627021456 0ustar www-datawww-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.rb0000644000004100000410000001074114645745627026714 0ustar www-datawww-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.rb0000644000004100000410000001115414645745627026177 0ustar www-datawww-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.rb0000644000004100000410000000052414645745627025116 0ustar www-datawww-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.rb0000644000004100000410000000326414645745627021706 0ustar www-datawww-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.rb0000644000004100000410000014313014645745627022337 0ustar www-datawww-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.rb0000644000004100000410000001253514645745627023422 0ustar www-datawww-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.rb0000644000004100000410000001427014645745627022766 0ustar www-datawww-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.rb0000644000004100000410000000245614645745627023413 0ustar www-datawww-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.rb0000644000004100000410000000374214645745627023722 0ustar www-datawww-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.rb0000644000004100000410000000166414645745627022514 0ustar www-datawww-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.rb0000644000004100000410000000540314645745627021121 0ustar www-datawww-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.rb0000644000004100000410000000335314645745627022312 0ustar www-datawww-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/0000755000004100000410000000000014645745627016055 5ustar www-datawww-datamechanize-2.10.1/test/htdocs/tc_charset.html0000644000004100000410000000017014645745627021060 0ustar www-datawww-data mechanize-2.10.1/test/htdocs/button.jpg0000644000004100000410000000172714645745627020101 0ustar www-datawww-dataJFIFHHExifMM*Created with The GIMPC  !"$"$Cd"(!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:|c c c c c c c c cmechanize-2.10.1/test/htdocs/meta_cookie.html0000644000004100000410000000060114645745627021217 0ustar www-datawww-data alt text no image mechanize-2.10.1/test/htdocs/index.html0000644000004100000410000000014514645745627020052 0ustar www-datawww-data Page Title

    Hello World!

    mechanize-2.10.1/test/htdocs/no_title_test.html0000644000004100000410000000012214645745627021612 0ustar www-datawww-data No title in the title tag mechanize-2.10.1/test/htdocs/tc_field_precedence.html0000644000004100000410000000054214645745627022672 0ustar www-datawww-data tc_field_precedence.html
    Ticky?
    mechanize-2.10.1/test/htdocs/form_select.html0000644000004100000410000000073614645745627021253 0ustar www-datawww-data

    mechanize-2.10.1/test/htdocs/relative/0000755000004100000410000000000014645745627017670 5ustar www-datawww-datamechanize-2.10.1/test/htdocs/relative/tc_relative_links.html0000644000004100000410000000132314645745627024256 0ustar www-datawww-data dot dot slash too many dots mechanize-2.10.1/test/htdocs/form_order_test.html0000644000004100000410000000051614645745627022142 0ustar www-datawww-data Page Title
    mechanize-2.10.1/test/htdocs/form_no_action.html0000644000004100000410000000056514645745627021745 0ustar www-datawww-data Page Title
    First Name
    mechanize-2.10.1/test/htdocs/tc_blank_form.html0000644000004100000410000000050414645745627021542 0ustar www-datawww-data
    mechanize-2.10.1/test/htdocs/tc_relative_links.html0000644000004100000410000000115614645745627022447 0ustar www-datawww-data forward mechanize-2.10.1/test/htdocs/frame_test.html0000644000004100000410000000167214645745627021102 0ustar www-datawww-data A simple frameset document <P>This frameset document contains: <UL> <LI><A href="/google.html">Some neat contents</A> <LI><A href="/form_test.html" class="bar">Form Test</A> <LI><A href="/file_upload.html">Some other neat contents</A> </UL> mechanize-2.10.1/test/htdocs/canonical_uri.html0000644000004100000410000000026414645745627021553 0ustar www-datawww-data test mechanize-2.10.1/test/htdocs/tc_follow_meta.html0000644000004100000410000000024414645745627021741 0ustar www-datawww-data This page has a meta refresh. mechanize-2.10.1/test/htdocs/link with space.html0000644000004100000410000000013214645745627021704 0ustar www-datawww-data This is a webpage that has a space in the filename. mechanize-2.10.1/test/htdocs/tc_form_action.html0000644000004100000410000000241714645745627021735 0ustar www-datawww-data Page Title

    Post Form 1

    First Name

    Post Form 2

    First Name

    Post Form 3

    First Name

    Post Form 4

    First Name

    mechanize-2.10.1/test/htdocs/tc_pretty_print.html0000644000004100000410000000121514645745627022173 0ustar www-datawww-data tc_pretty_print.html Google




    BODY assert_equal 3, page.frames.size assert_equal "frame1", page.frames[0].name assert_equal "/google.html", page.frames[0].src assert_equal "Google", page.frames[0].content.title assert_equal "frame2", page.frames[1].name assert_equal "/form_test.html", page.frames[1].src assert_equal "Page Title", page.frames[1].content.title assert_equal "frame3", page.frames[2].name assert_equal "/file_upload.html", page.frames[2].src assert_equal "File Upload Form", page.frames[2].content.title assert_equal %w[/google.html /file_upload.html], page.frames_with(search: '*[name=frame1], *[name=frame3]').map(&:src) end def test_iframes page = html_page <<-BODY A simple frameset document BODY assert_equal 1, page.iframes.size assert_equal "frame4", page.iframes.first.name assert_equal "/file_upload.html", page.iframes.first.src assert_equal "File Upload Form", page.iframes.first.content.title end unless RUBY_ENGINE == 'jruby' # NekoHTML does not parse IFRAME def test_image_with page = html_page <<-BODY BODY assert_equal "http://example/b.jpg", page.image_with(:src => 'b.jpg').url.to_s end def test_images_with page = html_page <<-BODY BODY images = page.images_with(:src => /jpg\Z/).map { |img| img.url.to_s } assert_equal %w[http://example/a.jpg http://example/b.jpg], images end def test_links page = html_page <<-BODY BODY assert_equal page.links.first.href, "foo.html" end def test_parser_no_attributes page = html_page <<-BODY Hello
    BODY # HACK weak assertion assert_kind_of Nokogiri::HTML::Document, page.root end def test_search_links page = html_page <<-BODY b a 6 BODY links = page.links_with(:search => "#spany a") assert_equal 2, links.size assert_equal "b.html", links[0].href assert_equal "b", links[0].text assert_equal "a.html", links[1].href assert_equal "a", links[1].text end def test_search_images page = html_page <<-BODY BODY { :search => "//img[@class='pretty']", :xpath => "//img[@class='pretty']", :css => "img.pretty", :class => "pretty", :dom_class => "pretty", }.each { |key, expr| images = page.images_with(key => expr) message = "selecting with #{key.inspect}" assert_equal 2, images.size assert_equal "pretty", images[0].dom_class, message assert_equal "a.jpg", images[0].src, message assert_equal "pretty", images[1].dom_class, message assert_equal "c.png", images[1].src, message } end def test_search_bad_selectors page = html_page <<-BODY foo BODY assert_empty page.images_with(:search => '//a') assert_empty page.links_with(:search => '//img') end def test_multiple_titles page = html_page <<-BODY HTML>TITLE SVGTITLE RDFDCTITLE BODY assert_equal page.title, "HTML>TITLE" end def test_frozen_string_body html = (<<~HTML).freeze Page Title

    Hello World

    HTML html_page(html) # refute_raises end end mechanize-2.10.1/test/test_mechanize_parser.rb0000644000004100000410000001541014645745627021475 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeParser < Mechanize::TestCase class P include Mechanize::Parser attr_accessor :filename attr_accessor :response attr_accessor :uri def initialize @uri = URI 'http://example' @full_path = false end end def setup super @parser = P.new end def test_extract_filename @parser.response = {} assert_equal 'index.html', @parser.extract_filename end def test_extract_filename_content_disposition @parser.uri = URI 'http://example/foo' @parser.response = { 'content-disposition' => 'attachment; filename=genome.jpeg' } assert_equal 'genome.jpeg', @parser.extract_filename end def test_extract_filename_content_disposition_bad @parser.uri = URI 'http://example/foo' @parser.response = { 'content-disposition' => "inline; filename*=UTF-8''X%20Y.jpg" } assert_equal 'foo.html', @parser.extract_filename @parser.response = { 'content-disposition' => "inline; filename=\"\"" } assert_equal 'foo.html', @parser.extract_filename end def test_extract_filename_content_disposition_path @parser.uri = URI 'http://example' @parser.response = { 'content-disposition' => 'attachment; filename="../genome.jpeg"' } assert_equal 'example/genome.jpeg', @parser.extract_filename(true) @parser.response = { 'content-disposition' => 'attachment; filename="foo/genome.jpeg"' } assert_equal 'example/genome.jpeg', @parser.extract_filename(true) end def test_extract_filename_content_disposition_path_windows @parser.uri = URI 'http://example' @parser.response = { 'content-disposition' => 'attachment; filename="..\\\\genome.jpeg"' } assert_equal 'example/genome.jpeg', @parser.extract_filename(true) @parser.response = { 'content-disposition' => 'attachment; filename="foo\\\\genome.jpeg"' } assert_equal 'example/genome.jpeg', @parser.extract_filename(true) end def test_extract_filename_content_disposition_full_path @parser.uri = URI 'http://example/foo' @parser.response = { 'content-disposition' => 'attachment; filename=genome.jpeg' } assert_equal 'example/genome.jpeg', @parser.extract_filename(true) end def test_extract_filename_content_disposition_quoted @parser.uri = URI 'http://example' @parser.response = { 'content-disposition' => 'attachment; filename="\"some \"file\""' } assert_equal '_some__file_', @parser.extract_filename end def test_extract_filename_content_disposition_special @parser.uri = URI 'http://example/foo' @parser.response = { 'content-disposition' => 'attachment; filename="/\\\\<>:\\"|?*"' } assert_equal '_______', @parser.extract_filename chars = (0..12).map { |c| c.chr }.join chars += "\\\r" chars += (14..31).map { |c| c.chr }.join @parser.response = { 'content-disposition' => "attachment; filename=\"#{chars}\"" } assert_equal '_' * 32, @parser.extract_filename end def test_extract_filename_content_disposition_windows_special @parser.uri = URI 'http://example' windows_special = %w[ AUX COM1 COM2 COM3 COM4 COM5 COM6 COM7 COM8 COM9 CON LPT1 LPT2 LPT3 LPT4 LPT5 LPT6 LPT7 LPT8 LPT9 NUL PRN ] windows_special.each do |special| @parser.response = { 'content-disposition' => "attachment; filename=#{special}" } assert_equal "_#{special}", @parser.extract_filename end end def test_extract_filename_content_disposition_empty @parser.uri = URI 'http://example' @parser.response = { 'content-disposition' => 'inline; filename="/"' } assert_equal '', @parser.extract_filename end def test_extract_filename_host @parser.response = {} @parser.uri = URI 'http://example' assert_equal 'example/index.html', @parser.extract_filename(true) end def test_extract_filename_special_character @parser.response = {} invisible = "\t\n\v\f\r" invisible.chars.each do |char| begin @parser.uri = URI "http://example/#{char}" assert_equal 'index.html', @parser.extract_filename, char.inspect rescue URI::InvalidURIError # ignore end end escaped = "<>\"\\|" escaped.chars.each do |char| escaped_char = CGI.escape char @parser.uri = URI "http://example/#{escaped_char}" assert_equal "#{escaped_char}.html", @parser.extract_filename, char end @parser.uri = URI "http://example/?" assert_equal 'index.html_', @parser.extract_filename, 'empty query' @parser.uri = URI "http://example/:" assert_equal '_.html', @parser.extract_filename, 'colon' @parser.uri = URI "http://example/*" assert_equal '_.html', @parser.extract_filename, 'asterisk' end def test_extract_filename_uri @parser.response = {} @parser.uri = URI 'http://example/foo' assert_equal 'foo.html', @parser.extract_filename @parser.uri += '/foo.jpg' assert_equal 'foo.jpg', @parser.extract_filename end def test_extract_filename_uri_full_path @parser.response = {} @parser.uri = URI 'http://example/foo' assert_equal 'example/foo.html', @parser.extract_filename(true) @parser.uri += '/foo.jpg' assert_equal 'example/foo.jpg', @parser.extract_filename(true) end def test_extract_filename_uri_query @parser.response = {} @parser.uri = URI 'http://example/?id=5' assert_equal 'index.html_id=5', @parser.extract_filename @parser.uri += '/foo.html?id=5' assert_equal 'foo.html_id=5', @parser.extract_filename end def test_extract_filename_uri_slash @parser.response = {} @parser.uri = URI 'http://example/foo/' assert_equal 'example/foo/index.html', @parser.extract_filename(true) @parser.uri += '/foo///' assert_equal 'example/foo/index.html', @parser.extract_filename(true) end def test_extract_filename_windows_special @parser.uri = URI 'http://example' @parser.response = {} windows_special = %w[ AUX COM1 COM2 COM3 COM4 COM5 COM6 COM7 COM8 COM9 CON LPT1 LPT2 LPT3 LPT4 LPT5 LPT6 LPT7 LPT8 LPT9 NUL PRN ] windows_special.each do |special| @parser.uri += "/#{special}" assert_equal "_#{special}.html", @parser.extract_filename end end def test_fill_header @parser.fill_header 'a' => 'b' expected = { 'a' => 'b' } assert_equal expected, @parser.response end def test_fill_header_nil @parser.fill_header nil assert_empty @parser.response end end mechanize-2.10.1/test/test_mechanize_http_auth_challenge.rb0000644000004100000410000000260414645745627024204 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeHttpAuthChallenge < Mechanize::TestCase def setup super @uri = URI 'http://example/' @AR = Mechanize::HTTP::AuthRealm @AC = Mechanize::HTTP::AuthChallenge @challenge = @AC.new 'Digest', { 'realm' => 'r' }, 'Digest realm=r' end def test_realm_basic @challenge.scheme = 'Basic' expected = @AR.new 'Basic', @uri, 'r' assert_equal expected, @challenge.realm(@uri + '/foo') end def test_realm_digest expected = @AR.new 'Digest', @uri, 'r' assert_equal expected, @challenge.realm(@uri + '/foo') end def test_realm_digest_case challenge = @AC.new 'Digest', { 'realm' => 'R' }, 'Digest realm=R' expected = @AR.new 'Digest', @uri, 'R' assert_equal expected, challenge.realm(@uri + '/foo') end def test_realm_unknown @challenge.scheme = 'Unknown' e = assert_raises Mechanize::Error do @challenge.realm(@uri + '/foo') end assert_equal 'unknown HTTP authentication scheme Unknown', e.message end def test_realm_name assert_equal 'r', @challenge.realm_name end def test_realm_name_case challenge = @AC.new 'Digest', { 'realm' => 'R' }, 'Digest realm=R' assert_equal 'R', challenge.realm_name end def test_realm_name_ntlm challenge = @AC.new 'Negotiate, NTLM' assert_nil challenge.realm_name end end mechanize-2.10.1/test/test_mechanize_pluggable_parser.rb0000644000004100000410000000265314645745627023524 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizePluggableParser < Mechanize::TestCase def setup super @pp = @mech.pluggable_parser end def test_aref @pp['text/html'] = Mechanize::Download assert_equal Mechanize::Download, @pp['text/html'] end def test_csv @pp.csv = Mechanize::Download assert_equal Mechanize::Download, @pp['text/csv'] end def test_html assert_equal Mechanize::Page, @pp['text/html'] @pp.html = Mechanize::Download assert_equal Mechanize::Download, @pp['text/html'] end def test_parser assert_equal Mechanize::XmlFile, @pp.parser('text/xml') assert_equal Mechanize::File, @pp.parser(nil) end def test_parser_mime @pp['image/png'] = :png assert_equal :png, @pp.parser('x-image/x-png') assert_equal :png, @pp.parser('image/png') assert_equal Mechanize::Image, @pp.parser('image') end def test_parser_bogus assert_nil @pp['bogus'] assert_equal Mechanize::File, @pp.parser('bogus') end def test_pdf @pp.pdf = Mechanize::Download assert_equal Mechanize::Download, @pp['application/pdf'] end def test_xml assert_equal Mechanize::XmlFile, @pp['text/xml'] assert_equal Mechanize::XmlFile, @pp['application/xml'] @pp.xml = Mechanize::Download assert_equal Mechanize::Download, @pp['text/xml'] assert_equal Mechanize::Download, @pp['application/xml'] end end mechanize-2.10.1/test/test_mechanize_headers.rb0000644000004100000410000000150014645745627021607 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeHeaders < Mechanize::TestCase def setup super @headers = Mechanize::Headers.new @headers['content-type'] = 'text/html' @headers['Content-encoding'] = 'gzip' @headers['SERVER'] = 'Apache/2.2' end def test_aref assert_equal('Apache/2.2', @headers['server']) assert_equal('text/html', @headers['Content-Type']) end def test_key? assert_equal(true, @headers.key?('content-Encoding')) end def test_canonical_each all_keys = ['Content-Type', 'Content-Encoding', 'Server'] keys = all_keys.dup @headers.canonical_each { |key, value| case keys.delete(key) when *all_keys # ok else flunk "unexpected key: #{key}" end } assert_equal([], keys) end end mechanize-2.10.1/test/test_mechanize_form_textarea.rb0000644000004100000410000000310514645745627023037 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeFormTextarea < Mechanize::TestCase def setup super @page = @mech.get("http://localhost/tc_textarea.html") end def test_empty_text_area form = @page.forms_with(:name => 'form1').first assert_equal('', form.field_with(:name => 'text1').value) form.text1 = 'Hello World' assert_equal('Hello World', form.field_with(:name => 'text1').value) page = @mech.submit(form) assert_equal(1, page.links.length) assert_equal('text1:Hello World', page.links[0].text) end def test_non_empty_textfield form = @page.forms_with(:name => 'form2').first assert_equal('sample text', form.field_with(:name => 'text1').value) page = @mech.submit(form) assert_equal(1, page.links.length) assert_equal('text1:sample text', page.links[0].text) end def test_multi_textfield form = @page.form_with(:name => 'form3') assert_equal(2, form.fields_with(:name => 'text1').length) assert_equal('', form.fields_with(:name => 'text1')[0].value) assert_equal('sample text', form.fields_with(:name => 'text1')[1].value) form.text1 = 'Hello World' assert_equal('Hello World', form.fields_with(:name => 'text1')[0].value) assert_equal('sample text', form.fields_with(:name => 'text1')[1].value) page = @mech.submit(form) assert_equal(2, page.links.length) link = page.links_with(:text => 'text1:sample text') assert_equal(1, link.length) link = page.links_with(:text => 'text1:Hello World') assert_equal(1, link.length) end end mechanize-2.10.1/test/test_mechanize_page_link.rb0000644000004100000410000002422714645745627022140 0ustar www-datawww-data# coding: utf-8 # frozen_string_literal: true require 'mechanize/test_case' puts "Nokogiri::VERSION_INFO: #{Nokogiri::VERSION_INFO}" class TestMechanizePageLink < Mechanize::TestCase WINDOWS_1255 = <<-HTML hi HTML BAD = <<-HTML.dup Bia\xB3ystok HTML BAD.force_encoding Encoding::BINARY if defined? Encoding SJIS_TITLE = "\x83\x65\x83\x58\x83\x67" SJIS_AFTER_TITLE = <<-HTML.dup #{SJIS_TITLE} HTML SJIS_AFTER_TITLE.force_encoding Encoding::BINARY if defined? Encoding SJIS_BAD_AFTER_TITLE = <<-HTML.dup #{SJIS_TITLE} HTML SJIS_BAD_AFTER_TITLE.force_encoding Encoding::BINARY if defined? Encoding UTF8_TITLE = 'テスト' UTF8 = <<-HTML #{UTF8_TITLE} HTML ENCODING_ERROR_CLASS = Nokogiri::XML::SyntaxError def setup super @uri = URI('http://example') @res = { 'content-type' => 'text/html' } @body = +'hi' end def util_page body = @body, res = @res Mechanize::Page.new @uri, res, body && body.force_encoding(Encoding::BINARY), 200, @mech end def skip_if_nkf_dependency if RUBY_ENGINE == 'jruby' meth = caller_locations(1,1).first.base_label skip "#{meth}: skipped because this feature currently depends on NKF" end end def test_override_content_type page = Mechanize::Page.new nil, {'content-type' => 'text/html'}, WINDOWS_1255 assert page assert_equal 'text/html; charset=windows-1255', page.content_type end def test_canonical_uri page = @mech.get("http://localhost/canonical_uri.html") assert_equal(URI("http://localhost/canonical_uri"), page.canonical_uri) page = @mech.get("http://localhost/file_upload.html") assert_nil page.canonical_uri end def test_canonical_uri_unescaped page = util_page(+<<-BODY) BODY assert_equal @uri + '/white%20space', page.canonical_uri end def test_charset_from_content_type charset = Mechanize::Page.__send__ :charset_from_content_type, 'text/html;charset=UTF-8' assert_equal 'UTF-8', charset end def test_charset_from_bad_content_type charset = Mechanize::Page.__send__ :charset_from_content_type, 'text/html' assert_nil charset end def test_encoding page = util_page WINDOWS_1255.dup assert_equal 'windows-1255', page.encoding end def test_encoding_charset_after_title page = util_page SJIS_AFTER_TITLE assert_equal false, page.encoding_error? assert_equal 'Shift_JIS', page.encoding end def test_encoding_charset_after_title_bad skip_if_nkf_dependency # https://gitlab.gnome.org/GNOME/libxml2/-/issues/543 skip if Nokogiri.uses_libxml?([">= 2.11.0", "< 2.12.0"]) page = util_page UTF8.dup assert_equal false, page.encoding_error? assert_equal "UTF-8", page.encoding end def test_encoding_charset_after_title_double_bad skip_if_nkf_dependency page = util_page SJIS_BAD_AFTER_TITLE assert_equal false, page.encoding_error? assert_equal 'SHIFT_JIS', page.encoding end def test_encoding_charset_bad skip_if_nkf_dependency # https://gitlab.gnome.org/GNOME/libxml2/-/issues/543 skip if Nokogiri.uses_libxml?([">= 2.11.0", "< 2.12.0"]) page = util_page(+"#{UTF8_TITLE}") page.encodings.replace %w[ UTF-8 Shift_JIS ] assert_equal false, page.encoding_error? assert_equal 'UTF-8', page.encoding end def test_encoding_meta_charset page = util_page(+"") assert_equal 'UTF-8', page.encoding end def test_encoding_equals page = util_page page.meta_refresh assert page.instance_variable_get(:@meta_refresh) page.encoding = 'UTF-8' assert_nil page.instance_variable_get(:@meta_refresh) assert_equal 'UTF-8', page.encoding assert_equal 'UTF-8', page.parser.encoding end def test_page_encoding_error? page = util_page page.parser.errors.clear assert_equal false, page.encoding_error? end def test_detect_libxml2error_indicate_encoding page = util_page page.parser.errors.clear # error in libxml2-2.7.8/parser.c, HTMLparser.c or parserInternals.c page.parser.errors = [ENCODING_ERROR_CLASS.new("Input is not proper UTF-8, indicate encoding !\n")] assert_equal true, page.encoding_error? end def test_detect_libxml2error_invalid_char page = util_page page.parser.errors.clear # error in libxml2-2.7.8/HTMLparser.c page.parser.errors = [ENCODING_ERROR_CLASS.new("Invalid char in CDATA 0x%X\n")] assert_equal true, page.encoding_error? end def test_detect_libxml2error_input_conversion_failed page = util_page page.parser.errors.clear # error in libxml2-2.7.8/encoding.c page.parser.errors = [ENCODING_ERROR_CLASS.new("input conversion failed due to input error\n")] assert_equal true, page.encoding_error? end def test_detect_libxml2error_which_unsupported_by_mechanize page = util_page page.parser.errors.clear # error in libxml2-2.7.8/HTMLparser.c page.parser.errors = [ENCODING_ERROR_CLASS.new("encoder error\n")] assert_equal false, page.encoding_error? end def test_encoding_equals_before_parser # document has a bad encoding information - windows-1255 page = util_page BAD # encoding is wrong, so user wants to force ISO-8859-2 page.encoding = 'ISO-8859-2' assert_equal false, page.encoding_error? assert_equal 'ISO-8859-2', page.encoding assert_equal 'ISO-8859-2', page.parser.encoding end def test_encoding_equals_after_parser # document has a bad encoding information - windows-1255 page = util_page BAD page.parser # autodetection sets encoding to windows-1255 assert_equal 'windows-1255', page.encoding # believe in yourself, not machine assert_equal false, page.encoding_error? # encoding is wrong, so user wants to force ISO-8859-2 page.encoding = 'ISO-8859-2' assert_equal false, page.encoding_error? assert_equal 'ISO-8859-2', page.encoding assert_equal 'ISO-8859-2', page.parser.encoding end def test_frames_with page = @mech.get("http://localhost/frame_test.html") assert_equal(3, page.frames.size) find_orig = page.frames.find_all { |f| f.name == 'frame1' } find1 = page.frames_with(:name => 'frame1') find_orig.zip(find1).each { |a,b| assert_equal(a, b) } end def test_links_with_dom_id page = @mech.get("http://localhost/tc_links.html") link = page.links_with(:dom_id => 'bold_aaron_link') link_by_id = page.links_with(:id => 'bold_aaron_link') assert_equal(1, link.length) assert_equal('Aaron Patterson', link.first.text) assert_equal(link, link_by_id) end def test_links_with_dom_class page = @mech.get("http://localhost/tc_links.html") link = page.links_with(:dom_class => 'thing_link') link_by_class = page.links_with(:class => 'thing_link') assert_equal(1, link.length) assert_equal(link, link_by_class) end def test_link_with_encoded_space page = @mech.get("http://localhost/tc_links.html") link = page.link_with(:text => 'encoded space') page = @mech.click link end def test_link_with_space page = @mech.get("http://localhost/tc_links.html") link = page.link_with(:text => 'not encoded space') page = @mech.click link end def test_link_with_unusual_characters page = @mech.get("http://localhost/tc_links.html") link = page.link_with(:text => 'unusual characters') @mech.click link # HACK no assertion end def test_links page = @mech.get("http://localhost/find_link.html") assert_equal(18, page.links.length) end unless RUBY_ENGINE == 'jruby' # NekoHTML does not parse FRAME outside of FRAMESET def test_links_with_bold page = @mech.get("http://localhost/tc_links.html") link = page.links_with(:text => /Bold Dude/) assert_equal(1, link.length) assert_equal('Bold Dude', link.first.text) assert_equal [], link.first.rel assert !link.first.rel?('me') assert !link.first.rel?('nofollow') link = page.links_with(:text => 'Aaron James Patterson') assert_equal(1, link.length) assert_equal('Aaron James Patterson', link.first.text) assert_equal ['me'], link.first.rel assert link.first.rel?('me') assert !link.first.rel?('nofollow') link = page.links_with(:text => 'Aaron Patterson') assert_equal(1, link.length) assert_equal('Aaron Patterson', link.first.text) assert_equal ['me', 'nofollow'], link.first.rel assert link.first.rel?('me') assert link.first.rel?('nofollow') link = page.links_with(:text => 'Ruby Rocks!') assert_equal(1, link.length) assert_equal('Ruby Rocks!', link.first.text) end def test_meta_refresh page = @mech.get("http://localhost/find_link.html") assert_equal(3, page.meta_refresh.length) assert_equal(%w{ http://www.drphil.com/ http://www.upcase.com/ http://tenderlovemaking.com/ }.sort, page.meta_refresh.map { |x| x.href.downcase }.sort) end def test_title page = util_page assert_equal('hi', page.title) end def test_title_none page = util_page(+'') # invalid HTML assert_nil(page.title) end def test_page_decoded_with_charset page = util_page @body, 'content-type' => 'text/html; charset=EUC-JP' assert_equal 'EUC-JP', page.encoding assert_equal 'EUC-JP', page.parser.encoding end def test_form page = @mech.get("http://localhost/tc_form_action.html") form = page.form(:name => 'post_form1') assert form yielded = false form = page.form(:name => 'post_form1') { |f| yielded = true assert f assert_equal(form, f) } assert yielded form_by_action = page.form(:action => '/form_post?a=b&b=c') assert form_by_action assert_equal(form, form_by_action) end end mechanize-2.10.1/test/test_mechanize_link.rb0000644000004100000410000001246014645745627021140 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeLink < Mechanize::TestCase def test_search page = @mech.get("http://localhost/find_link.html") link = page.link_with(text: "Form Test") assert_equal('Form Test', link.text) link_with_search = page.link_with(search: "//*[text()='Form Test']") assert_equal(link, link_with_search) link_with_xpath = page.link_with(xpath: "//*[text()='Form Test']") assert_equal(link, link_with_xpath) link_with_css = page.link_with(css: ".formtest") assert_equal(link, link_with_css) link_with_class = page.link_with(class: "formtest") assert_equal(link, link_with_class) end def test_click page = @mech.get("http://localhost/frame_test.html") link = page.link_with(:text => "Form Test") assert_equal('Form Test', link.text) page = link.click assert_equal("http://localhost/form_test.html", @mech.history.last.uri.to_s) end unless RUBY_ENGINE == 'jruby' # NekoHTML does not parse body of NOFRAMES def test_click_bang page = @mech.get("http://localhost/frame_test.html") link = page.link_with!(:text => "Form Test") assert_equal('Form Test', link.text) page = link.click assert_equal("http://localhost/form_test.html", @mech.history.last.uri.to_s) end unless RUBY_ENGINE == 'jruby' # NekoHTML does not parse body of NOFRAMES def test_click_base page = @mech.get("http://google.com/tc_base_link.html") page = page.links.first.click assert @mech.visited?("http://localhost/index.html") end def test_click_unsupported_scheme page = @mech.get("http://google.com/tc_links.html") link = page.link_with(:text => 'javascript link') assert_raises Mechanize::UnsupportedSchemeError do begin link.click rescue Mechanize::UnsupportedSchemeError => error assert_equal 'javascript', error.scheme assert_equal "javascript:new_page('1')", error.uri.to_s raise end end @mech.scheme_handlers['javascript'] = lambda { |my_link, my_page| URI.parse('http://localhost/tc_links.html') } link.click # HACK no assertion end def test_click_unexiting_link page = @mech.get("http://google.com/tc_links.html") assert_raises NoMethodError do page.link_with(:text => 'no link').click end begin page.link_with!(:text => 'no link').click rescue => e assert_instance_of Mechanize::ElementNotFoundError, e assert_kind_of Mechanize::Page, e.source assert_equal :link, e.element assert_kind_of Hash, e.conditions assert_equal 'no link', e.conditions[:text] end end def test_click_empty_href page = @mech.get("http://google.com/tc_links.html?q=test#anchor") link = page.link_with(:text => 'empty href') new_page = link.click assert_equal "http://google.com/tc_links.html?q=test", new_page.uri.to_s end def test_text_alt_text page = @mech.get("http://localhost/alt_text.html") assert_equal(5, page.links.length) assert_equal(1, page.meta_refresh.length) assert_equal '', page.meta_refresh.first.text assert_equal 'alt text', page.link_with(:href => 'alt_text.html').text assert_equal '', page.link_with(:href => 'no_alt_text.html').text assert_equal 'no image', page.link_with(:href => 'no_image.html').text assert_equal '', page.link_with(:href => 'no_text.html').text assert_equal '', page.link_with(:href => 'nil_alt_text.html').text end def test_uri_escaped doc = Nokogiri::HTML::Document.new node = Nokogiri::XML::Node.new('foo', doc) node['href'] = 'http://foo.bar/%20baz' link = Mechanize::Page::Link.new(node, nil, nil) assert_equal 'http://foo.bar/%20baz', link.uri.to_s end def test_uri_no_path page = @mech.get("http://localhost/relative/tc_relative_links.html") page = page.link_with(:text => 'just the query string').click assert_equal('http://localhost/relative/tc_relative_links.html?a=b', page.uri.to_s) end unless RUBY_ENGINE == 'jruby' # NekoHTML does not parse IFRAME def test_uri_weird doc = Nokogiri::HTML::Document.new node = Nokogiri::XML::Node.new('foo', doc) node['href'] = 'http://foo.bar/ baz' link = Mechanize::Page::Link.new(node, nil, nil) assert_equal 'http://foo.bar/%20baz', link.uri.to_s end def test_uri_weird_with_fragment doc = Nokogiri::HTML::Document.new node = Nokogiri::XML::Node.new('foo', doc) node['href'] = 'http://foo.bar/ baz#уважение' link = Mechanize::Page::Link.new(node, nil, nil) assert_equal '%D1%83%D0%B2%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5', link.uri.fragment end def test_bad_uri_raise_compatible_exception doc = Nokogiri::HTML::Document.new node = Nokogiri::XML::Node.new('foo', doc) node['href'] = 'http://http:foo.bar/ baz' link = Mechanize::Page::Link.new(node, nil, nil) assert_raises URI::InvalidURIError do link.uri end end def test_resolving_full_uri page = @mech.get("http://localhost/frame_test.html") link = page.link_with(:text => "Form Test") assert_equal "/form_test.html", link.uri.to_s assert_equal "http://localhost/form_test.html", link.resolved_uri.to_s end unless RUBY_ENGINE == 'jruby' # NekoHTML does not parse body of NOFRAMES end mechanize-2.10.1/test/test_mechanize_xml_file.rb0000644000004100000410000000113514645745627021777 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeXmlFile < Mechanize::TestCase def setup super uri = URI 'http://example.com/foo.xml' @xml = Mechanize::XmlFile.new uri, nil, <<-XML Ruby Perl XML end def test_xml assert_kind_of Nokogiri::XML::Document, @xml.xml end def test_search assert_equal ['Ruby', 'Perl'], @xml.search('language').map { |n| n.text } end def test_at assert_equal 'Perl', @xml.at('//language[2]').text end endmechanize-2.10.1/test/test_mechanize_page_image.rb0000644000004100000410000001320514645745627022257 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizePageImage < Mechanize::TestCase def setup super @uri = URI 'http://example/' @src = (@uri + 'a.jpg').to_s @empty_page = Mechanize::Page.new(@uri, nil, '', 200, @mech) end def img attributes img = node 'img', attributes Mechanize::Page::Image.new img, @empty_page end def test_initialize image = img("src" => "a.jpg", "alt" => "alt", "width" => "100", "height" => "200", "title" => "title", "id" => "id", "class" => "class") assert_equal "a.jpg", image.src assert_equal "alt", image.alt assert_equal "100", image.width assert_equal "200", image.height assert_equal "title", image.title assert_equal "id", image.dom_id assert_equal "class", image.dom_class end def test_initialize_no_attributes image = img({}) assert_nil image.src assert_nil image.alt assert_nil image.width assert_nil image.height assert_nil image.title assert_nil image.dom_id assert_nil image.dom_class end def test_caption assert_equal "", img("src" => @src).caption assert_equal "alt", img("src" => @src, "alt" => "alt").caption assert_equal "title", img("src" => @src, "title" => "title").caption assert_equal "title", img("src" => @src, "alt" => "alt", "title" => "title").caption end def test_url assert_equal ".jpg", img('src' => @src).extname assert_equal "http://example/a.jpg", img('src' => @src).url.to_s assert_equal "http://example/a%20.jpg", img('src' => 'http://example/a .jpg' ).url.to_s end def test_url_base page = html_page <<-BODY BODY assert_equal "http://other.example/a.jpg", page.images.first.url end def test_extname assert_equal ".jpg", img("src" => "a.jpg").extname assert_equal ".PNG", img("src" => "a.PNG").extname assert_equal ".aaa", img("src" => "unknown.aaa").extname assert_equal "", img("src" => "nosuffiximage").extname assert_nil img("width" => "1", "height" => "1").extname assert_equal ".jpg", img("src" => "a.jpg?cache_buster").extname end def test_mime_type assert_equal "image/jpeg", img("src" => "a.jpg").mime_type assert_equal "image/png", img("src" => "a.PNG").mime_type assert_nil img("src" => "unknown.aaa").mime_type assert_nil img("src" => "nosuffiximage").mime_type end def test_fetch image = img "src" => "http://localhost/button.jpg" fetched = image.fetch assert_equal fetched, @mech.page assert_equal "http://localhost/button.jpg", fetched.uri.to_s assert_equal "http://example/", requests.first['Referer'] assert @mech.visited? "http://localhost/button.jpg" end def test_fetch_referer_http_page_rel_src # | rel-src http-src https-src # http page | *page* page page # https page | page empty empty page = html_page '' page.images.first.fetch assert_equal 'http', page.uri.scheme assert_equal true, page.images.first.relative? assert_equal "http://example/", requests.first['Referer'] end def test_fetch_referer_http_page_abs_src # | rel-src http-src https-src # http page | page *page* *page* # https page | page empty empty page = html_page '' page.images.first.fetch assert_equal 'http', page.uri.scheme assert_equal false, page.images.first.relative? assert_equal "http://example/", requests.first['Referer'] end def test_fetch_referer_https_page_rel_src # | rel-src http-src https-src # http page | page page page # https page | *page* empty empty page = html_page '' page.uri = URI 'https://example/' page.images.first.fetch assert_equal 'https', page.uri.scheme assert_equal true, page.images.first.relative? assert_equal "https://example/", requests.first['Referer'] end def test_fetch_referer_https_page_abs_src # | rel-src http-src https-src # http page | page page page # https page | page *empty* *empty* page = html_page '' page.uri = URI 'https://example/' page.images.first.fetch assert_equal 'https', page.uri.scheme assert_equal false, page.images.first.relative? assert_nil requests.first['Referer'] end def test_image_referer_http_page_abs_src page = html_page '' assert_equal 'http', page.uri.scheme assert_equal @uri, page.images.first.image_referer.uri end def test_image_referer_http_page_rel_src page = html_page '' assert_equal 'http', page.uri.scheme assert_equal @uri, page.images.first.image_referer.uri end def test_image_referer_https_page_abs_src page = html_page '' page.uri = URI 'https://example/' assert_equal 'https', page.uri.scheme assert_nil page.images.first.image_referer.uri end def test_image_referer_https_page_rel_src page = html_page '' page.uri = URI 'https://example/' assert_equal 'https', page.uri.scheme assert_equal URI('https://example/'), page.images.first.image_referer.uri end def test_no_src_attribute page = html_page '' page.uri = URI 'https://example/' assert_equal URI('https://example/'), page.images.first.url end end mechanize-2.10.1/test/test_mechanize_http_auth_realm.rb0000644000004100000410000000212114645745627023354 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeHttpAuthRealm < Mechanize::TestCase def setup super @uri = URI 'http://example/' @AR = Mechanize::HTTP::AuthRealm @realm = @AR.new 'Digest', @uri, 'r' end def test_initialize assert_equal 'r', @realm.realm realm = @AR.new 'Digest', @uri, 'R' refute_equal 'r', realm.realm realm = @AR.new 'Digest', @uri, 'R' assert_equal 'R', realm.realm realm = @AR.new 'Digest', @uri, nil assert_nil realm.realm end def test_equals2 other = @realm.dup assert_equal @realm, other other = @AR.new 'Basic', @uri, 'r' refute_equal @realm, other other = @AR.new 'Digest', URI('http://other.example/'), 'r' refute_equal @realm, other other = @AR.new 'Digest', @uri, 'R' refute_equal @realm, other other = @AR.new 'Digest', @uri, 's' refute_equal @realm, other end def test_hash h = {} h[@realm] = 1 other = @realm.dup assert_equal 1, h[other] other = @AR.new 'Basic', @uri, 'r' assert_nil h[other] end end mechanize-2.10.1/test/test_mechanize_form_file_upload.rb0000644000004100000410000000066414645745627023514 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeFormFileUpload < Mechanize::TestCase def test_file_name field = node 'input' field = Mechanize::Form::FileUpload.new field, 'a&b' assert_equal 'a&b', field.file_name end def test_file_name_entity field = node 'input' field = Mechanize::Form::FileUpload.new field, 'a&b' assert_equal 'a&b', field.file_name end end mechanize-2.10.1/test/test_mechanize_file_response.rb0000644000004100000410000000256214645745627023042 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeFileResponse < Mechanize::TestCase def test_file_path res = Mechanize::FileResponse.new("/path/to/foo.html") assert_equal("/path/to/foo.html", res.file_path) end def test_content_type Tempfile.open %w[pi .nothtml] do |tempfile| res = Mechanize::FileResponse.new tempfile.path assert_nil res['content-type'] end Tempfile.open %w[pi .xhtml] do |tempfile| res = Mechanize::FileResponse.new tempfile.path assert_equal 'text/html', res['content-type'] end Tempfile.open %w[pi .html] do |tempfile| res = Mechanize::FileResponse.new tempfile.path assert_equal 'text/html', res['Content-Type'] end end def test_read_body Tempfile.open %w[pi .html] do |tempfile| tempfile.write("asdfasdfasdf") tempfile.close res = Mechanize::FileResponse.new(tempfile.path) res.read_body do |input| assert_equal("asdfasdfasdf", input) end end end def test_read_body_does_not_allow_command_injection skip if windows? in_tmpdir do FileUtils.touch('| ruby -rfileutils -e \'FileUtils.touch("vul.txt")\'') res = Mechanize::FileResponse.new('| ruby -rfileutils -e \'FileUtils.touch("vul.txt")\'') res.read_body { |_| } refute_operator(File, :exist?, "vul.txt") end end end mechanize-2.10.1/test/test_mechanize_form_select_list.rb0000644000004100000410000000310014645745627023527 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeFormSelectList < Mechanize::TestCase def setup super page = html_page <<-BODY
    BODY form = page.forms.first @select = form.fields.first end def test_inspect assert_match "value: 2", @select.inspect end def test_option_with option = @select.option_with :value => '1' assert_equal '1', option.value end def test_options_with options = @select.options_with :value => /[12]/ assert_equal 2, options.length end def test_query_value assert_equal [%w[select 2]], @select.query_value @select.select_all assert_equal [%w[select 6]], @select.query_value end def test_select_all @select.select_all assert_equal "6", @select.value end def test_select_none @select.select_none assert_equal "1", @select.value end def test_selected_options assert_equal [@select.options[1]], @select.selected_options @select.options.last.click assert_equal [@select.options.last], @select.selected_options end def test_value assert_equal "2", @select.value end def test_value_equals @select.value = %w[a 1 2] assert_equal "a", @select.value end end mechanize-2.10.1/test/test_mechanize_cookie_jar.rb0000644000004100000410000004475114645745627022320 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' require 'fileutils' class TestMechanizeCookieJar < Mechanize::TestCase def setup super @jar = Mechanize::CookieJar.new @jar.extend Minitest::Assertions def @jar.add(*args) capture_io { super } end def @jar.jar(*args) result = nil capture_io { result = super } result end def @jar.save_as(*args) result = nil capture_io { result = super } result end def @jar.clear!(*args) result = nil capture_io { result = super } result end end def cookie_values(options = {}) { :name => 'Foo', :value => 'Bar', :path => '/', :expires => Time.now + (10 * 86400), :for_domain => true, :domain => 'rubygems.org' }.merge(options) end def test_two_cookies_same_domain_and_name_different_paths url = URI 'http://rubygems.org/' cookie = Mechanize::Cookie.new(cookie_values) @jar.add(url, cookie) @jar.add(url, Mechanize::Cookie.new(cookie_values(:path => '/onetwo'))) assert_equal(1, @jar.cookies(url).length) assert_equal 2, @jar.cookies(URI('http://rubygems.org/onetwo')).length end def test_domain_case url = URI 'http://rubygems.org/' # Add one cookie with an expiration date in the future cookie = Mechanize::Cookie.new(cookie_values) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length) @jar.add(url, Mechanize::Cookie.new( cookie_values(:domain => 'rubygems.Org', :name => 'aaron'))) assert_equal(2, @jar.cookies(url).length) url2 = URI 'http://rubygems.oRg/' assert_equal(2, @jar.cookies(url2).length) end def test_host_only url = URI.parse('http://rubygems.org/') @jar.add(url, Mechanize::Cookie.new( cookie_values(:domain => 'rubygems.org', :for_domain => false))) assert_equal(1, @jar.cookies(url).length) assert_equal(1, @jar.cookies(URI('http://rubygems.org/')).length) assert_equal(1, @jar.cookies(URI('https://rubygems.org/')).length) assert_equal(0, @jar.cookies(URI('http://www.rubygems.org/')).length) end def test_empty_value values = cookie_values(:value => "") url = URI 'http://rubygems.org/' # Add one cookie with an expiration date in the future cookie = Mechanize::Cookie.new(values) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length) @jar.add url, Mechanize::Cookie.new(values.merge(:domain => 'rubygems.Org', :name => 'aaron')) assert_equal(2, @jar.cookies(url).length) url2 = URI 'http://rubygems.oRg/' assert_equal(2, @jar.cookies(url2).length) end def test_add_future_cookies url = URI 'http://rubygems.org/' # Add one cookie with an expiration date in the future cookie = Mechanize::Cookie.new(cookie_values) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length) # Add the same cookie, and we should still only have one @jar.add(url, Mechanize::Cookie.new(cookie_values)) assert_equal(1, @jar.cookies(url).length) # Make sure we can get the cookie from different paths assert_equal(1, @jar.cookies(URI('http://rubygems.org/login')).length) # Make sure we can't get the cookie from different domains assert_equal(0, @jar.cookies(URI('http://google.com/')).length) end def test_add_multiple_cookies url = URI 'http://rubygems.org/' # Add one cookie with an expiration date in the future cookie = Mechanize::Cookie.new(cookie_values) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length) # Add the same cookie, and we should still only have one @jar.add(url, Mechanize::Cookie.new(cookie_values(:name => 'Baz'))) assert_equal(2, @jar.cookies(url).length) # Make sure we can get the cookie from different paths assert_equal(2, @jar.cookies(URI('http://rubygems.org/login')).length) # Make sure we can't get the cookie from different domains assert_equal(0, @jar.cookies(URI('http://google.com/')).length) end def test_add_rejects_cookies_that_do_not_contain_an_embedded_dot url = URI 'http://rubygems.org/' tld_cookie = Mechanize::Cookie.new(cookie_values(:domain => '.org')) @jar.add(url, tld_cookie) # single dot domain is now treated as no domain # single_dot_cookie = Mechanize::Cookie.new(cookie_values(:domain => '.')) # @jar.add(url, single_dot_cookie) assert_equal(0, @jar.cookies(url).length) end def test_fall_back_rules_for_local_domains url = URI 'http://www.example.local' tld_cookie = Mechanize::Cookie.new(cookie_values(:domain => '.local')) @jar.add(url, tld_cookie) assert_equal(0, @jar.cookies(url).length) sld_cookie = Mechanize::Cookie.new(cookie_values(:domain => '.example.local')) @jar.add(url, sld_cookie) assert_equal(1, @jar.cookies(url).length) end def test_add_makes_exception_for_localhost url = URI 'http://localhost' tld_cookie = Mechanize::Cookie.new(cookie_values(:domain => 'localhost')) @jar.add(url, tld_cookie) assert_equal(1, @jar.cookies(url).length) end def test_add_cookie_for_the_parent_domain url = URI 'http://x.foo.com' cookie = Mechanize::Cookie.new(cookie_values(:domain => '.foo.com')) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length) end def test_add_does_not_reject_cookies_from_a_nested_subdomain url = URI 'http://y.x.foo.com' cookie = Mechanize::Cookie.new(cookie_values(:domain => '.foo.com')) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length) end def test_cookie_without_leading_dot_does_not_cause_substring_match url = URI 'http://arubygems.org/' cookie = Mechanize::Cookie.new(cookie_values(:domain => 'rubygems.org')) @jar.add(url, cookie) assert_equal(0, @jar.cookies(url).length) end def test_cookie_without_leading_dot_matches_subdomains url = URI 'http://admin.rubygems.org/' cookie = Mechanize::Cookie.new(cookie_values(:domain => 'rubygems.org')) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length) end def test_cookies_with_leading_dot_match_subdomains url = URI 'http://admin.rubygems.org/' @jar.add(url, Mechanize::Cookie.new(cookie_values(:domain => '.rubygems.org'))) assert_equal(1, @jar.cookies(url).length) end def test_cookies_with_leading_dot_match_parent_domains url = URI 'http://rubygems.org/' @jar.add(url, Mechanize::Cookie.new(cookie_values(:domain => '.rubygems.org'))) assert_equal(1, @jar.cookies(url).length) end def test_cookies_with_leading_dot_match_parent_domains_exactly url = URI 'http://arubygems.org/' @jar.add(url, Mechanize::Cookie.new(cookie_values(:domain => '.rubygems.org'))) assert_equal(0, @jar.cookies(url).length) end def test_cookie_for_ipv4_address_matches_the_exact_ipaddress url = URI 'http://192.168.0.1/' cookie = Mechanize::Cookie.new(cookie_values(:domain => '192.168.0.1')) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length) end def test_cookie_for_ipv4_address_does_not_cause_subdomain_match url = URI 'http://192.168.0.1/' cookie = Mechanize::Cookie.new(cookie_values(:domain => '.0.1')) @jar.add(url, cookie) assert_equal(0, @jar.cookies(url).length) end def test_cookie_for_ipv6_address_matches_the_exact_ipaddress url = URI 'http://[fe80::0123:4567:89ab:cdef]/' cookie = Mechanize::Cookie.new(cookie_values(:domain => '[fe80::0123:4567:89ab:cdef]')) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length) end def test_cookies_dot url = URI 'http://www.host.example/' @jar.add(url, Mechanize::Cookie.new(cookie_values(:domain => 'www.host.example'))) url = URI 'http://wwwxhost.example/' assert_equal(0, @jar.cookies(url).length) end def test_clear_bang url = URI 'http://rubygems.org/' # Add one cookie with an expiration date in the future cookie = Mechanize::Cookie.new(cookie_values) @jar.add(url, cookie) @jar.add(url, Mechanize::Cookie.new(cookie_values(:name => 'Baz'))) assert_equal(2, @jar.cookies(url).length) @jar.clear! assert_equal(0, @jar.cookies(url).length) end def test_save_cookies_yaml url = URI 'http://rubygems.org/' # Add one cookie with an expiration date in the future cookie = Mechanize::Cookie.new(cookie_values) s_cookie = Mechanize::Cookie.new(cookie_values(:name => 'Bar', :expires => nil)) @jar.add(url, cookie) @jar.add(url, s_cookie) @jar.add(url, Mechanize::Cookie.new(cookie_values(:name => 'Baz'))) assert_equal(3, @jar.cookies(url).length) in_tmpdir do value = @jar.save_as("cookies.yml") assert_same @jar, value jar = Mechanize::CookieJar.new jar.load("cookies.yml") assert_equal(2, jar.cookies(url).length) end assert_equal(3, @jar.cookies(url).length) end def test_save_session_cookies_yaml url = URI 'http://rubygems.org/' # Add one cookie with an expiration date in the future cookie = Mechanize::Cookie.new(cookie_values) s_cookie = Mechanize::Cookie.new(cookie_values(:name => 'Bar', :expires => nil)) @jar.add(url, cookie) @jar.add(url, s_cookie) @jar.add(url, Mechanize::Cookie.new(cookie_values(:name => 'Baz'))) assert_equal(3, @jar.cookies(url).length) in_tmpdir do @jar.save_as("cookies.yml", :format => :yaml, :session => true) jar = Mechanize::CookieJar.new jar.load("cookies.yml") assert_equal(3, jar.cookies(url).length) end assert_equal(3, @jar.cookies(url).length) end def test_save_cookies_cookiestxt url = URI 'http://rubygems.org/' # Add one cookie with an expiration date in the future cookie = Mechanize::Cookie.new(cookie_values) s_cookie = Mechanize::Cookie.new(cookie_values(:name => 'Bar', :expires => nil)) @jar.add(url, cookie) @jar.add(url, s_cookie) @jar.add(url, Mechanize::Cookie.new(cookie_values(:name => 'Baz'))) assert_equal(3, @jar.cookies(url).length) in_tmpdir do @jar.save_as("cookies.txt", :cookiestxt) assert_match(/\A# (?:Netscape )?HTTP Cookie File$/, File.read("cookies.txt")) jar = Mechanize::CookieJar.new jar.load("cookies.txt", :cookiestxt) assert_equal(2, jar.cookies(url).length) end in_tmpdir do @jar.save_as("cookies.txt", :cookiestxt, :session => true) assert_match(/\A# (?:Netscape )?HTTP Cookie File$/, File.read("cookies.txt")) jar = Mechanize::CookieJar.new jar.load("cookies.txt", :cookiestxt) assert_equal(3, jar.cookies(url).length) end assert_equal(3, @jar.cookies(url).length) end def test_expire_cookies url = URI 'http://rubygems.org/' # Add one cookie with an expiration date in the future cookie = Mechanize::Cookie.new(cookie_values) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length) # Add a second cookie @jar.add(url, Mechanize::Cookie.new(cookie_values(:name => 'Baz'))) assert_equal(2, @jar.cookies(url).length) # Make sure we can get the cookie from different paths assert_equal(2, @jar.cookies(URI('http://rubygems.org/login')).length) # Expire the first cookie @jar.add(url, Mechanize::Cookie.new( cookie_values(:expires => Time.now - (10 * 86400)))) assert_equal(1, @jar.cookies(url).length) # Expire the second cookie @jar.add(url, Mechanize::Cookie.new( cookie_values( :name => 'Baz', :expires => Time.now - (10 * 86400)))) assert_equal(0, @jar.cookies(url).length) end def test_session_cookies values = cookie_values(:expires => nil) url = URI 'http://rubygems.org/' # Add one cookie with an expiration date in the future cookie = Mechanize::Cookie.new(values) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length) # Add a second cookie @jar.add(url, Mechanize::Cookie.new(values.merge(:name => 'Baz'))) assert_equal(2, @jar.cookies(url).length) # Make sure we can get the cookie from different paths assert_equal(2, @jar.cookies(URI('http://rubygems.org/login')).length) # Expire the first cookie @jar.add(url, Mechanize::Cookie.new(values.merge(:expires => Time.now - (10 * 86400)))) assert_equal(1, @jar.cookies(url).length) # Expire the second cookie @jar.add(url, Mechanize::Cookie.new( values.merge(:name => 'Baz', :expires => Time.now - (10 * 86400)))) assert_equal(0, @jar.cookies(url).length) # When given a URI with a blank path, CookieJar#cookies should return # cookies with the path '/': url = URI 'http://rubygems.org' assert_equal '', url.path assert_equal(0, @jar.cookies(url).length) # Now add a cookie with the path set to '/': @jar.add(url, Mechanize::Cookie.new(values.merge( :name => 'has_root_path', :path => '/'))) assert_equal(1, @jar.cookies(url).length) end def test_paths values = cookie_values(:path => "/login", :expires => nil) url = URI 'http://rubygems.org/login' # Add one cookie with an expiration date in the future cookie = Mechanize::Cookie.new(values) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length) # Add a second cookie @jar.add(url, Mechanize::Cookie.new(values.merge( :name => 'Baz' ))) assert_equal(2, @jar.cookies(url).length) # Make sure we don't get the cookie in a different path assert_equal(0, @jar.cookies(URI('http://rubygems.org/hello')).length) assert_equal(0, @jar.cookies(URI('http://rubygems.org/')).length) # Expire the first cookie @jar.add(url, Mechanize::Cookie.new(values.merge( :expires => Time.now - (10 * 86400)))) assert_equal(1, @jar.cookies(url).length) # Expire the second cookie @jar.add(url, Mechanize::Cookie.new(values.merge( :name => 'Baz', :expires => Time.now - (10 * 86400)))) assert_equal(0, @jar.cookies(url).length) end def test_save_and_read_cookiestxt url = URI 'http://rubygems.org/' # Add one cookie with an expiration date in the future cookie = Mechanize::Cookie.new(cookie_values) @jar.add(url, cookie) @jar.add(url, Mechanize::Cookie.new(cookie_values(:name => 'Baz'))) assert_equal(2, @jar.cookies(url).length) in_tmpdir do @jar.save_as("cookies.txt", :cookiestxt) @jar.clear! @jar.load("cookies.txt", :cookiestxt) end assert_equal(2, @jar.cookies(url).length) end def test_save_and_read_cookiestxt_with_session_cookies url = URI 'http://rubygems.org/' @jar.add(url, Mechanize::Cookie.new(cookie_values(:expires => nil))) in_tmpdir do @jar.save_as("cookies.txt", :cookiestxt) @jar.clear! @jar.load("cookies.txt", :cookiestxt) end assert_equal(0, @jar.cookies(url).length) end def test_prevent_command_injection_when_saving skip if windows? url = URI 'http://rubygems.org/' path = '| ruby -rfileutils -e \'FileUtils.touch("vul.txt")\'' @jar.add(url, Mechanize::Cookie.new(cookie_values)) in_tmpdir do @jar.save_as(path, :cookiestxt) assert_equal(false, File.exist?('vul.txt')) end end def test_prevent_command_injection_when_loading skip if windows? url = URI 'http://rubygems.org/' path = '| ruby -rfileutils -e \'FileUtils.touch("vul.txt")\'' @jar.add(url, Mechanize::Cookie.new(cookie_values)) in_tmpdir do @jar.save_as("cookies.txt", :cookiestxt) @jar.clear! assert_raises Errno::ENOENT do @jar.load(path, :cookiestxt) end assert_equal(false, File.exist?('vul.txt')) end end def test_save_and_read_expired_cookies url = URI 'http://rubygems.org/' @jar.jar['rubygems.org'] = {} @jar.add url, Mechanize::Cookie.new(cookie_values) # HACK no assertion end def test_ssl_cookies # thanks to michal "ocher" ochman for reporting the bug responsible for this test. values = cookie_values(:expires => nil) values_ssl = values.merge(:name => 'Baz', :domain => "#{values[:domain]}:443") url = URI 'https://rubygems.org/login' cookie = Mechanize::Cookie.new(values) @jar.add(url, cookie) assert_equal(1, @jar.cookies(url).length, "did not handle SSL cookie") cookie = Mechanize::Cookie.new(values_ssl) @jar.add(url, cookie) assert_equal(2, @jar.cookies(url).length, "did not handle SSL cookie with :443") end def test_secure_cookie nurl = URI 'http://rubygems.org/login' surl = URI 'https://rubygems.org/login' ncookie = Mechanize::Cookie.new(cookie_values(:name => 'Foo1')) scookie = Mechanize::Cookie.new(cookie_values(:name => 'Foo2', :secure => true)) @jar.add(nurl, ncookie) @jar.add(nurl, scookie) @jar.add(surl, ncookie) @jar.add(surl, scookie) assert_equal('Foo1', @jar.cookies(nurl).map { |c| c.name }.sort.join(' ') ) assert_equal('Foo1 Foo2', @jar.cookies(surl).map { |c| c.name }.sort.join(' ') ) end def test_save_cookies_cookiestxt_subdomain top_url = URI 'http://rubygems.org/' subdomain_url = URI 'http://admin.rubygems.org/' # cookie1 is for *.rubygems.org; cookie2 is only for rubygems.org, no subdomains cookie1 = Mechanize::Cookie.new(cookie_values) cookie2 = Mechanize::Cookie.new(cookie_values(:name => 'Boo', :for_domain => false)) @jar.add(top_url, cookie1) @jar.add(top_url, cookie2) assert_equal(2, @jar.cookies(top_url).length) assert_equal(1, @jar.cookies(subdomain_url).length) in_tmpdir do @jar.save_as("cookies.txt", :cookiestxt) jar = Mechanize::CookieJar.new jar.load("cookies.txt", :cookiestxt) # HACK test the format assert_equal(2, jar.cookies(top_url).length) assert_equal(1, jar.cookies(subdomain_url).length) # Check that we actually wrote the file correctly (not just that we were # able to read what we wrote): # # * Cookies that only match exactly the domain specified must not have a # leading dot, and must have FALSE as the second field. # * Cookies that match subdomains may have a leading dot, and must have # TRUE as the second field. cookies_txt = File.readlines("cookies.txt") assert_equal(1, cookies_txt.grep( /^rubygems\.org\tFALSE/ ).length) assert_equal(1, cookies_txt.grep( /^\.rubygems\.org\tTRUE/ ).length) end assert_equal(2, @jar.cookies(top_url).length) assert_equal(1, @jar.cookies(subdomain_url).length) end end mechanize-2.10.1/test/test_mechanize_form_encoding.rb0000644000004100000410000000632014645745627023012 0ustar www-datawww-data# coding: utf-8 # frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeFormEncoding < Mechanize::TestCase # See also: tests of Util.from_native_charset # Encoding test should do with non-utf-8 characters INPUTTED_VALUE = "テスト" # "test" in Japanese UTF-8 encoding CONTENT_ENCODING = 'Shift_JIS' # one of Japanese encoding encoded_value = "\x83\x65\x83\x58\x83\x67".dup.force_encoding(::Encoding::SHIFT_JIS) # "test" in Japanese Shift_JIS encoding EXPECTED_QUERY = "first_name=#{CGI.escape(encoded_value)}&first_name=&gender=&green%5Beggs%5D=" ENCODING_ERRORS = [EncodingError, Encoding::ConverterNotFoundError] # and so on ENCODING_LOG_MESSAGE = /INFO -- : form encoding: Shift_JIS/ INVALID_ENCODING = 'UTF-eight' def set_form_with_encoding(enc) page = @mech.get("http://localhost/form_set_fields.html") form = page.forms.first form.encoding = enc form['first_name'] = INPUTTED_VALUE form end def test_form_encoding_returns_accept_charset page = @mech.get("http://localhost/rails_3_encoding_hack_form_test.html") form = page.forms.first accept_charset = form.form_node['accept-charset'] assert accept_charset assert_equal accept_charset, form.encoding refute_equal page.encoding, form.encoding end def test_form_encoding_returns_page_encoding_when_no_accept_charset page = @mech.get("http://localhost/form_set_fields.html") form = page.forms.first accept_charset = form.form_node['accept-charset'] assert_nil accept_charset refute_equal accept_charset, form.encoding assert_equal page.encoding, form.encoding end def test_form_encoding_equals_sets_new_encoding page = @mech.get("http://localhost/form_set_fields.html") form = page.forms.first refute_equal CONTENT_ENCODING, form.encoding form.encoding = CONTENT_ENCODING assert_equal CONTENT_ENCODING, form.encoding end def test_form_encoding_returns_nil_when_no_page_in_initialize # this sequence is seen at Mechanize#post(url, query_hash) node = {} # Create a fake form class << node def search(*args); []; end end node['method'] = 'POST' node['enctype'] = 'application/x-www-form-urlencoded' form = Mechanize::Form.new(node) assert_nil form.encoding end def test_post_form_with_form_encoding form = set_form_with_encoding CONTENT_ENCODING form.submit # we can not use "links.find{|l| l.text == 'key:val'}" assertion here # because the link text encoding is always UTF-8 regaredless of html encoding assert EXPECTED_QUERY, @mech.page.at('div#query').inner_text end def test_post_form_with_problematic_encoding form = set_form_with_encoding INVALID_ENCODING assert_raises(*ENCODING_ERRORS){ form.submit } end def test_form_ignore_encoding_error_is_true form = set_form_with_encoding INVALID_ENCODING form.ignore_encoding_error = true form.submit # HACK no assertions end def test_post_form_logs_form_encoding sio = StringIO.new @mech.log = Logger.new(sio) @mech.log.level = Logger::INFO form = set_form_with_encoding CONTENT_ENCODING form.submit assert_match ENCODING_LOG_MESSAGE, sio.string @mech.log = nil end end mechanize-2.10.1/test/test_multi_select.rb0000644000004100000410000001025214645745627020646 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class MultiSelectTest < Mechanize::TestCase def setup super @page = @mech.get("http://localhost/form_multi_select.html") @form = @page.forms.first end def test_option_with o = @form.field_with(:name => 'list').option_with(:value => '1') assert_equal '1', o.value end def test_options_with os = @form.field_with(:name => 'list').options_with(:value => /1|2/) assert_equal ['1', '2'].sort, os.map { |x| x.value }.sort end def test_select_none page = @mech.get("http://localhost/form_multi_select.html") form = page.forms.first form.field_with(:name => 'list').select_none page = @mech.submit(form) assert_equal(0, page.links.length) end def test_select_all page = @mech.get("http://localhost/form_multi_select.html") form = page.forms.first form.field_with(:name => 'list').select_all page = @mech.submit(form) assert_equal(6, page.links.length) assert_equal(1, page.links_with(:text => 'list:1').length) assert_equal(1, page.links_with(:text => 'list:2').length) assert_equal(1, page.links_with(:text => 'list:3').length) assert_equal(1, page.links_with(:text => 'list:4').length) assert_equal(1, page.links_with(:text => 'list:5').length) assert_equal(1, page.links_with(:text => 'list:6').length) end def test_click_all page = @mech.get("http://localhost/form_multi_select.html") form = page.forms.first form.field_with(:name => 'list').options.each { |o| o.click } page = @mech.submit(form) assert_equal(5, page.links.length) assert_equal(1, page.links_with(:text => 'list:1').length) assert_equal(1, page.links_with(:text => 'list:3').length) assert_equal(1, page.links_with(:text => 'list:4').length) assert_equal(1, page.links_with(:text => 'list:5').length) assert_equal(1, page.links_with(:text => 'list:6').length) end def test_select_default page = @mech.get("http://localhost/form_multi_select.html") form = page.forms.first page = @mech.submit(form) assert_equal(1, page.links.length) assert_equal(1, page.links_with(:text => 'list:2').length) end def test_select_one page = @mech.get("http://localhost/form_multi_select.html") form = page.forms.first form.list = 'Aaron' assert_equal(['Aaron'], form.list) page = @mech.submit(form) assert_equal(1, page.links.length) assert_equal('list:Aaron', page.links.first.text) end def test_select_two page = @mech.get("http://localhost/form_multi_select.html") form = page.forms.first form.list = ['1', 'Aaron'] page = @mech.submit(form) assert_equal(2, page.links.length) assert_equal(1, page.links_with(:text => 'list:1').length) assert_equal(1, page.links_with(:text => 'list:Aaron').length) end def test_select_three page = @mech.get("http://localhost/form_multi_select.html") form = page.forms.first form.list = ['1', '2', '3'] page = @mech.submit(form) assert_equal(3, page.links.length) assert_equal(1, page.links_with(:text => 'list:1').length) assert_equal(1, page.links_with(:text => 'list:2').length) assert_equal(1, page.links_with(:text => 'list:3').length) end def test_select_three_twice page = @mech.get("http://localhost/form_multi_select.html") form = page.forms.first form.list = ['1', '2', '3'] form.list = ['1', '2', '3'] page = @mech.submit(form) assert_equal(3, page.links.length) assert_equal(1, page.links_with(:text => 'list:1').length) assert_equal(1, page.links_with(:text => 'list:2').length) assert_equal(1, page.links_with(:text => 'list:3').length) end def test_select_with_click page = @mech.get("http://localhost/form_multi_select.html") form = page.forms.first form.list = ['1', 'Aaron'] form.field_with(:name => 'list').options[3].tick assert_equal(['1', 'Aaron', '4'].sort, form.list.sort) page = @mech.submit(form) assert_equal(3, page.links.length) assert_equal(1, page.links_with(:text => 'list:1').length) assert_equal(1, page.links_with(:text => 'list:Aaron').length) assert_equal(1, page.links_with(:text => 'list:4').length) end end mechanize-2.10.1/test/test_mechanize_form_image_button.rb0000644000004100000410000000050414645745627023677 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeFormImageButton < Mechanize::TestCase def test_query_value button = Mechanize::Form::ImageButton.new 'name' => 'image_button' assert_equal [%w[image_button.x 0], %w[image_button.y 0]], button.query_value end end mechanize-2.10.1/test/test_mechanize_response_read_error.rb0000644000004100000410000000117114645745627024242 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeResponseReadError < Mechanize::TestCase def setup super @error = 'error message' @response = Response.new @response['content-length'] = 3 @body_io = StringIO.new 'body' end def test_force_parse @response['content-type'] = 'text/html' uri = URI 'http://example/' e = Mechanize::ResponseReadError.new @error, @response, @body_io, uri, @mech page = e.force_parse assert_kind_of Mechanize::Page, page assert_equal 'body', page.body assert_equal @mech, page.mech end end mechanize-2.10.1/test/test_mechanize_form.rb0000644000004100000410000007745614645745627021166 0ustar www-datawww-data# frozen_string_literal: true require 'mechanize/test_case' class TestMechanizeForm < Mechanize::TestCase def setup super @uri = URI 'http://example' @page = page @uri @form = Mechanize::Form.new node('form', 'name' => @NAME), @mech, @page end def test_action form = Mechanize::Form.new node('form', 'action' => '?a=b&b=c') assert_equal '?a=b&b=c', form.action end def test_add_button_to_query button = Mechanize::Form::Button.new node('input', 'type' => 'submit') e = assert_raises ArgumentError do @form.add_button_to_query button end assert_equal "#{button.inspect} does not belong to the same page " \ "as the form \"#{@NAME}\" in #{@uri}", e.message end def test_aset assert_empty @form.keys @form['intarweb'] = 'Aaron' assert_equal 'Aaron', @form['intarweb'] end def test_aset_exists page = html_page <<-BODY Page Title
    BODY form = page.form_with(:name => 'post_form') assert_equal %w[first first], form.keys form['first'] = 'Aaron' assert_equal 'Aaron', form['first'] assert_equal ['Aaron', ''], form.values end def test_build_query_blank_form page = @mech.get('http://localhost/tc_blank_form.html') form = page.forms.first query = form.build_query assert(query.length > 0) assert query.all? { |x| x[1] == '' } end def test_build_query_blank_input_name html = Nokogiri::HTML <<-HTML
    HTML form = Mechanize::Form.new html.at('form'), @mech, @page assert_equal [], form.build_query end def test_build_query_radio_button_duplicate html = Nokogiri::HTML <<-HTML
    HTML form = Mechanize::Form.new html.at('form'), @mech, @page query = form.build_query assert_equal [%w[name a]], query end def test_build_query_radio_button_multiple_checked html = Nokogiri::HTML <<-HTML
    HTML form = Mechanize::Form.new html.at('form'), @mech, @page e = assert_raises Mechanize::Error do form.build_query end assert_equal 'radiobuttons "a, b" are checked in the "name" group, ' \ 'only one is allowed', e.message end def test_method_missing_get page = html_page <<-BODY
    BODY form = page.forms.first assert_equal 'some value', form.not_a_method end def test_method_missing_set page = html_page <<-BODY
    BODY form = page.forms.first form.not_a_method = 'some value' assert_equal [%w[not_a_method some\ value]], form.build_query end def test_parse_buttons page = html_page <<-BODY