# frozen_string_literal: true # # ronin-support-web - A web support library for ronin-rb. # # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) # # ronin-support-web is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ronin-support-web is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with ronin-support-web. If not, see . # require 'ronin/support/network/http' require 'addressable/uri' require 'nokogiri' require 'json' module Ronin module Support module Web # # Web Agent represents a stripped-down web browser, which can request # URLs, follow redirects, and parse responses. # # ## Features # # * Automatically follows redirects. # * Provides low-level HTTP methods. # * Provides high-level methods for requesting and parsing HTML, XML, or # JSON. # * Maintains a persistent connection pool. # # ## Anti-Features # # * Does not cache files or write to the disk. # * Does not evaluate JavaScript. # class Agent # # Base-class for all {Agent} exceptions. # class Error < RuntimeError end # # Indicates that too many redirects were encountered in succession. # class TooManyRedirects < Error end # # Indicates that the response does not have a compatible or expected # `Content-Type` header. # class ContentTypeError < Error end # The proxy to send requests through. # # @return [URI::HTTP, Addressable::URI, nil] attr_reader :proxy # The `User-Agent` header value. # # @return [String, nil] attr_reader :user_agent # Maximum number of redirects to follow. # # @return [Integer] attr_reader :max_redirects # # Initializes the Web agent. # # @param [String, URI::HTTP, Addressable::URI, nil] proxy # The optional proxy to send requests through. # # @param [String, :random, :chrome, :chrome_linux, :chrome_macos, :chrome_windows, :chrome_iphone, :chrome_ipad, :chrome_android, :firefox, :firefox_linux, :firefox_macos, :firefox_windows, :firefox_iphone, :firefox_ipad, :firefox_android, :safari, :safari_macos, :safari_iphone, :safari_ipad, :edge, :linux, :macos, :windows, :iphone, :ipad, :android, nil] user_agent # The default `User-Agent` string to add to each request. # # @param [Boolean, Hash{Symbol => Object}, nil] ssl # Additional SSL/TLS configuration. # # @option ssl [String, nil] :ca_bundle # The path to the CA bundle directory or file. # # @option ssl [OpenSSL::X509::Store, nil] :cert_store # The certificate store to use for the SSL/TLS connection. # # @option ssl [Array<(name, version, bits, alg_bits)>, nil] :ciphers # The accepted ciphers to use for the SSL/TLS connection. # # @option ssl [Integer, nil] :timeout # The connection timeout limit. # # @option ssl [1, 1.1, 1.2, Symbol, nil] :version # The desired SSL/TLS version. # # @option ssl [1, 1.1, 1.2, Symbol, nil] :min_version # The minimum SSL/TLS version. # # @option ssl [1, 1.1, 1.2, Symbol, nil] :max_version # The maximum SSL/TLS version. # # @option ssl [Proc, nil] :verify_callback # The callback to use when verifying the server's certificate. # # @option ssl [Integer, nil] :verify_depth # The verification depth limit. # # @option ssl [:none, :peer, :fail_if_no_peer_cert, true, false, Integer, nil] :verify # The verification mode. # # @option ssl [Boolean, nil] :verify_hostname # Indicates whether to verify the server's hostname. # def initialize(follow_redirects: true, max_redirects: 20, # HTTP options proxy: Support::Network::HTTP.proxy, ssl: nil, user_agent: Support::Network::HTTP.user_agent) @follow_redirects = follow_redirects @max_redirects = max_redirects # HTTP options @proxy = proxy @ssl = ssl @user_agent = user_agent @sessions = {} end # # Indicates whether redirects will automatically be followed. # # @return [Boolean] # def follow_redirects? @follow_redirects end # # @!macro request_kwargs # @option kwargs [String, nil] :query # The query-string to append to the request path. # # @option kwargs [Hash, nil] :query_params # The query-params to append to the request path. # # @option kwargs [String, nil] :user # The user to authenticate as. # # @option kwargs [String, nil] :password # The password to authenticate with. # # @option kwargs [Hash{Symbol,String => String}, nil] :headers # Additional HTTP headers to use for the request. # # @option kwargs [String, :text, :xml, :html, :json, nil] :content_type # The `Content-Type` header value for the request. # If a Symbol is given it will be resolved to a common MIME type: # * `:text` - `text/plain` # * `:xml` - `text/xml` # * `:html` - `text/html` # * `:json` - `application/json` # # @option kwargs [String, :text, :xml, :html, :json, nil] :accept # The `Accept` header value for the request. # If a Symbol is given it will be resolved to a common MIME type: # * `:text` - `text/plain` # * `:xml` - `text/xml` # * `:html` - `text/html` # * `:json` - `application/json` # # @option kwargs [String, Hash{String => String}, Ronin::Support::Network::HTTP::Cookie, nil] :cookie # Additional `Cookie` header. # * If a `Hash` is given, it will be converted to a `String` using # [Ronin::Support::Network::HTTP::Cookie](https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP/Cookie.html). # * If the cookie value is empty, the `Cookie` header will not be # set. # # @option kwargs [String, nil] :body # The body of the request. # # @option kwargs [Hash, String, nil] :form_data # The form data that may be sent in the body of the request. # # @option kwargs [#to_json, nil] :json # The JSON data that will be sent in the body of the request. # Will also default the `Content-Type` header to # `application/json`, unless already set. # # # Performs and arbitrary HTTP request. # # @param [Symbol, String] method # The HTTP method to use for the request. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @raise [ArgumentError] # The `:method` option did not match a known `Net::HTTP` request # class. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#request-instance_method # # @api public # def http_request(method,url,**kwargs,&block) uri = normalize_url(url) session_for(uri).request( method, uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Sends an arbitrary HTTP request and returns the response status. # # @param [Symbol, String] method # The HTTP method to use for the request. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @return [Integer] # The status code of the response. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#response_status-instance_method # # @api public # def http_response_status(method=:head,url,**kwargs) uri = normalize_url(url) session_for(uri).response_status( method, uri.request_uri, user: uri.user, password: uri.password, **kwargs ) end # # Sends a HTTP request and determines if the response status was 200. # # @param [Symbol, String] method # The HTTP method to use for the request. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @return [Boolean] # Indicates that the response status was 200. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#ok%3F-instance_method # # @api public # def http_ok?(method=:head,url,**kwargs) uri = normalize_url(url) session_for(uri).ok?( method, uri.request_uri, user: uri.user, password: uri.password, **kwargs ) end # # Sends an arbitrary HTTP request and returns the response headers. # # @param [Symbol, String] method # The HTTP method to use for the request. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @return [Hash{String => String}] # The response headers. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#response_headers-instance_method # # @api public # def http_response_headers(method=:head,url,**kwargs) uri = normalize_url(url) session_for(uri).response_headers( method, uri.request_uri, user: uri.user, password: uri.password, **kwargs ) end # # Sends an HTTP request and returns the `Server` header. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @return [String, nil] # The `Server` header. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#server_header-instance_method # # @api public # def http_server_header(url,**kwargs) uri = normalize_url(url) session_for(uri).server_header( user: uri.user, password: uri.password, path: uri.request_uri, **kwargs ) end # # Sends an HTTP request and returns the `X-Powered-By` header. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @return [String, nil] # The `X-Powered-By` header. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#powered_by_header-instance_method # # @api public # def http_powered_by_header(url,**kwargs) uri = normalize_url(url) session_for(uri).powered_by_header( user: uri.user, password: uri.password, path: uri.request_uri, **kwargs ) end # # Sends an arbitrary HTTP request and returns the response body. # # @param [Symbol, String] method # The HTTP method to use for the request. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @return [String] # The response body. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#response_body-instance_method # # @api public # def http_response_body(method=:get,url,**kwargs) uri = normalize_url(url) session_for(uri).response_body( method, uri.request_uri, user: uri.user, password: uri.password, **kwargs ) end # # Performs a `COPY` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#copy-instance_method # # @api public # def http_copy(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).copy( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Performs a `DELETE` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#delete-instance_method # # @api public # def http_delete(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).delete( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Performs a `GET` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#get-instance_method # # @api public # def http_get(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).get( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Performs a `GET` request for the given URI and returns the response # headers. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @return [Hash{String => String}] # The response headers. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#get_headers-instance_method # # @api public # def http_get_headers(url,**kwargs) uri = normalize_url(url) session_for(uri).get_headers( uri.request_uri, user: uri.user, password: uri.password, **kwargs ) end # # Sends an HTTP request and returns the parsed `Set-Cookie` # header(s). # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @return [Array, nil] # The parsed `SetCookie` header(s). # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#get_cookies-instance_method # # @api public # def http_get_cookies(url,**kwargs) uri = normalize_url(url) session_for(uri).get_cookies( uri.request_uri, user: uri.user, password: uri.password, **kwargs ) end # # Performs a `GET` request for the given URI and returns the response # body. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @return [String] # The response body. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#get_body-instance_method # # @api public # def http_get_body(url,**kwargs) uri = normalize_url(url) session_for(uri).get_body( uri.request_uri, user: uri.user, password: uri.password, **kwargs ) end # # Performs a `HEAD` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#head-instance_method # # @api public # def http_head(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).head( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Performs a `LOCK` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#lock-instance_method # # @api public # def http_lock(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).lock( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Performs a `MKCOL` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#mkcol-instance_method # # @api public # def http_mkcol(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).mkcol( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Performs a `MOVE` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#move-instance_method # # @api public # def http_move(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).move( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Performs a `OPTIONS` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#options-instance_method # # @api public # def http_options(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).options( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Performs a `OPTIONS` HTTP request for the given URI and parses the # `Allow` response header. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @return [Array] # The allowed HTTP request methods for the given URL. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#allowed_methods-instance_method # # @api public # def http_allowed_methods(url,**kwargs) uri = normalize_url(url) session_for(uri).allowed_methods( uri.request_uri, user: uri.user, password: uri.password, **kwargs ) end # # Performs a `PATCH` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#patch-instance_method # # @api public # def http_patch(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).patch( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Performs a `POST` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#post-instance_method # # @api public # def http_post(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).post( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Performs a `POST` request on the given URI and returns the response # headers. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @return [Hash{String => String}] # The response headers. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#post_headers-instance_method # # @api public # def http_post_headers(url,**kwargs) uri = normalize_url(url) session_for(uri).post_headers( uri.request_uri, user: uri.user, password: uri.password, **kwargs ) end # # Performs a `POST` request for the given URI and returns the # response body. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @return [String] # The response body. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#post_body-instance_method # # @api public # def http_post_body(url,**kwargs) uri = normalize_url(url) session_for(uri).post_body( uri.request_uri, user: uri.user, password: uri.password, **kwargs ) end # # Performs a `PROPFIND` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#propfind-instance_method # # @api public # def http_propfind(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).propfind( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end alias http_prop_find http_propfind # # Performs a `PROPPATCH` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#proppatch-instance_method # # @api public # def http_proppatch(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).proppatch( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end alias http_prop_patch http_proppatch # # Performs a `PUT` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#put-instance_method # # @api public # def http_put(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).put( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Performs a `TRACE` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#trace-instance_method # # @api public # def http_trace(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).trace( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Performs a `UNLOCK` request for the given URI. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @see https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Network/HTTP.html#unlock-instance_method # # @api public # def http_unlock(url,**kwargs,&block) uri = normalize_url(url) session_for(uri).unlock( uri.request_uri, user: uri.user, password: uri.password, **kwargs, &block ) end # # Gets a URL and returns the response. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP GET request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @raise [TooManyRedirects] # Maximum number of redirects reached. # # @note This method will follow redirects by default. # # @example # response = agent.get('https://example.com/') # # => # # def get(url, follow_redirects: @follow_redirects, max_redirects: @max_redirects, **kwargs) response = http_get(url,**kwargs) if follow_redirects && response.kind_of?(Net::HTTPRedirection) redirect_count = 0 while response.kind_of?(Net::HTTPRedirection) if redirect_count >= max_redirects raise(TooManyRedirects,"maximum number of redirects reached: #{url.inspect}") end location = response['Location'] response = http_get(location) redirect_count += 1 end end yield response if block_given? return response end # # Gets the URL and returns the parsed HTML. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP GET request for. # # @!macro request_kwargs # # @return [Nokogiri::HTML::Document] # The parsed HTML response. # # @raise [ContentTypeError] # Did not receive a response with a `Content-Type` of `text/html`. # # @raise [TooManyRedirects] # Maximum number of redirects reached. # # @note This method will follow redirects by default. # # @example # doc = agent.get_html('https://example.com/page.html') # # => # # def get_html(url,**kwargs) response = get(url,**kwargs) unless response.content_type.include?('text/html') raise(ContentTypeError,"response 'Content-Type' was not 'text/html': #{response.content_type.inspect}") end return Nokogiri::HTML(response.body) end # # Gets the URL and returns the parsed XML. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP GET request for. # # @!macro request_kwargs # # @return [Nokogiri::XML::Document] # The parsed XML response. # # @raise [ContentTypeError] # Did not receive a response with a `Content-Type` of `text/xml`. # # @raise [TooManyRedirects] # Maximum number of redirects reached. # # @note This method will follow redirects by default. # # @example # doc = agent.get_xml('https://example.com/data.xml') # # => # # def get_xml(url,**kwargs) response = get(url,**kwargs) unless response.content_type.include?('text/xml') raise(ContentTypeError,"response 'Content-Type' was not 'text/xml': #{response.content_type.inspect}") end return Nokogiri::XML(response.body) end # # Gets the URL and returns the parsed JSON. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP GET request for. # # @!macro request_kwargs # # @return [Hash{String => Object}, Array] # The parsed JSON. # # @raise [ContentTypeError] # Did not receive a response with a `Content-Type` of # `application/json`. # # @raise [TooManyRedirects] # Maximum number of redirects reached. # # @note This method will follow redirects by default. # # @example # json = agent.get_json('https://example.com/data.json') # # => {...} # def get_json(url,**kwargs) response = get(url,**kwargs) unless response.content_type.include?('application/json') raise(ContentTypeError,"response 'Content-Type' was not 'application/json': #{response.content_type.inspect}") end return ::JSON.parse(response.body) end # # Performs an HTTP POST to the URL. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP GET request for. # # @!macro request_kwargs # # @yield [response] # If a block is given it will be passed the received HTTP response. # # @yieldparam [Net::HTTPResponse] response # The received HTTP response object. # # @return [Net::HTTPResponse] # The HTTP response object. # # @raise [TooManyRedirects] # Maximum number of redirects reached. # # @note # If the response is an HTTP redirect, then {#get} will be called to # follow any redirects. # # @example # response = agent.post('https://example.com/form', form_data: {'foo' => 'bar'}) # # => # # def post(url, follow_redirects: @follow_redirects, max_redirects: @max_redirects, **kwargs) response = http_post(url,**kwargs) if follow_redirects && response.kind_of?(Net::HTTPRedirection) location = response['Location'] response = begin get(location, follow_redirects: follow_redirects, max_redirects: max_redirects - 1) rescue TooManyRedirects raise(TooManyRedirects,"maximum number of redirects reached: #{url.inspect}") end end yield response if block_given? return response end # # Performs an HTTP POST to the URL and parses the HTML response. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP POST request for. # # @!macro request_kwargs # # @return [Nokogiri::HTML::Document] # The parsed HTML response. # # @raise [TooManyRedirects] # Maximum number of redirects reached. # # @raise [ContentTypeError] # Did not receive a response with a `Content-Type` of # `text/html`. # # @note # If the response is an HTTP redirect, then {#get} will be called to # follow any redirects. # # @example Send a POST request and parses the HTML response: # doc = agent.post_html 'https://example.com/form', form_data: {foo: 'bar'}) # # => # # def post_html(url,**kwargs) response = post(url,**kwargs) unless response.content_type.include?('text/html') raise(ContentTypeError,"response 'Content-Type' was not 'text/html': #{response.content_type.inspect}") end return Nokogiri::HTML(response.body) end # # Performs an HTTP POST to the URL and parses the XML response. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP POST request for. # # @!macro request_kwargs # # @return [Nokogiri::XML::Document] # The parsed XML response. # # @raise [TooManyRedirects] # Maximum number of redirects reached. # # @raise [ContentTypeError] # Did not receive a response with a `Content-Type` of # `text/xml`. # # @note # If the response is an HTTP redirect, then {#get} will be called to # follow any redirects. # # @example Send a POST request to the form and parses the XML response: # doc = agent.post_xml 'https://example.com/form', form_data: {foo: 'bar'} # # => # # def post_xml(url,**kwargs) response = post(url,**kwargs) unless response.content_type.include?('text/xml') raise(ContentTypeError,"response 'Content-Type' was not 'application/json': #{response.content_type.inspect}") end return Nokogiri::XML(response.body) end # # Performs an HTTP POST to the URL and parses the JSON response. # # @param [URI::HTTP, Addressable::URI, String] url # The URL to create the HTTP POST request for. # # @!macro request_kwargs # # @return [Hash{String => Object}, Array] # The parses JSON response. # # @raise [TooManyRedirects] # Maximum number of redirects reached. # # @raise [ContentTypeError] # Did not receive a response with a `Content-Type` of # `application/json`. # # @note # If the response is an HTTP redirect, then {#get} will be called to # follow any redirects. # # @example Send a POST request to the form and parse the JSON response: # json = agent.post_json 'https://example.com/form', form_data: {foo: 'bar'} # # => {...} # # @example Send a POST request containing JSON and parse the JSON response: # json = agent.post_json 'https://example.com/api/end-point', json: {foo: 'bar'} # # => {...} # def post_json(url,**kwargs) response = post(url,**kwargs) unless response.content_type.include?('application/json') raise(ContentTypeError,"response 'Content-Type' was not 'application/json': #{response.content_type.inspect}") end return ::JSON.parse(response.body) end private # # Normalizes a URL. # # @param [URI::HTTP, Addressable::URI, String, Object] url # The URL or URI to normalize. # # @return [URI::HTTP, Addressable::URI] # The parsed URL. # def normalize_url(url) case url when URI::HTTP, Addressable::URI then url when String then Addressable::URI.parse(url) else raise(ArgumentError,"url must be a URI::HTTP, Addressable::URI, or a String: #{url.inspect}") end end # # Fetches an existing HTTP session or creates a new one for the given # URI. # # @param [URI::HTTP] uri # The URL to retrieve or create an HTTP session for. # # @return [Ronin::Support::Network::HTTP] # The HTTP session. # def session_for(uri) key = [uri.scheme, uri.host, uri.port] @sessions[key] ||= Support::Network::HTTP.connect_uri( uri, proxy: @proxy, ssl: (@ssl if uri.scheme == 'https'), user_agent: @user_agent ) end end end end end