ronin-rb/ronin-support

View on GitHub
lib/ronin/support/network/http/request.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# frozen_string_literal: true
#
# Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# ronin-support 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 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.  If not, see <https://www.gnu.org/licenses/>.
#

require 'ronin/support/network/http/user_agents'
require 'ronin/support/network/http/cookie'

require 'net/http'
require 'uri/query_params'

module Ronin
  module Support
    module Network
      class HTTP
        #
        # Handles building HTTP request objects.
        #
        # @api private
        #
        # @since 1.0.0
        #
        module Request
          #
          # Builds the [Request-URI], aka path + query string, for a HTTP
          # request.
          #
          # [Request-URI]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
          #
          # @param [String] path
          #   The path of the request.
          #
          # @param [String, nil] query
          #   The optional query string for the request.
          #
          # @param [Hash{Symbol => Object},
          #         Hash{String => Object}, nil] query_params
          #   Additional query params for the request.
          #
          # @return [String]
          #
          def self.request_uri(path, query: nil, query_params: nil)
            if query_params
              query = URI::QueryParams.dump(query_params)
            end

            if query
              # append the query-string onto the path
              path += if path.include?('?') then "&#{query}"
                      else                       "?#{query}"
                      end
            end

            return path
          end

          # Request methods and `Net::HTTP` request classes.
          METHODS = {
            copy:      Net::HTTP::Copy,
            delete:    Net::HTTP::Delete,
            get:       Net::HTTP::Get,
            head:      Net::HTTP::Head,
            lock:      Net::HTTP::Lock,
            mkcol:     Net::HTTP::Mkcol,
            move:      Net::HTTP::Move,
            options:   Net::HTTP::Options,
            patch:     Net::HTTP::Patch,
            post:      Net::HTTP::Post,
            propfind:  Net::HTTP::Propfind,
            proppatch: Net::HTTP::Proppatch,
            put:       Net::HTTP::Put,
            trace:     Net::HTTP::Trace,
            unlock:    Net::HTTP::Unlock
          }

          #
          # Creates a new `Net::HTTP` request.
          #
          # @param [:copy, :delete, :get, :head, :lock, :mkcol, :move,
          #         :options, :patch, :post, :propfind, :proppatch, :put,
          #         :trace, :unlock] method
          #   The HTTP request method to use.
          #
          # @param [String] path
          #
          # @param [String, nil] query
          #   The query-string to append to the request path.
          #
          # @param [Hash, nil] query_params
          #   The query-params to append to the request path.
          #
          # @param [String, nil] user
          #   The user to authenticate as.
          #
          # @param [String, nil] password
          #   The password to authenticate with.
          #
          # @param [Hash{Symbol => String}, Hash{String => String}, nil] headers
          #   Additional HTTP header names and values to add to the request.
          #
          # @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 `User-Agent` header value for the request.
          #
          # @param [String, Hash{String => String}, Cookie, nil] cookie
          #   Additional `Cookie` header. If a `Hash` is given, it will be
          #   converted to a `String` using {Cookie}. If the cookie value is
          #   empty, the `Cookie` header will not be set.
          #
          # @param [String, nil] body
          #   The body of the request.
          #
          # @param [Hash, String, nil] form_data
          #   The form data that may be sent in the body of the request.
          #
          # @return [Net::HTTP::Copy,
          #          Net::HTTP::Delete,
          #          Net::HTTP::Get,
          #          Net::HTTP::Head,
          #          Net::HTTP::Lock,
          #          Net::HTTP::Mkcol,
          #          Net::HTTP::Move,
          #          Net::HTTP::Options,
          #          Net::HTTP::Patch,
          #          Net::HTTP::Post,
          #          Net::HTTP::Propfind,
          #          Net::HTTP::Proppatch,
          #          Net::HTTP::Put,
          #          Net::HTTP::Trace,
          #          Net::HTTP::Unlock]
          #   The built HTTP request object.
          #
          def self.build(method,path, # query string keyword arguments
                                      query:        nil,
                                      query_params: nil,
                                      # Basic-Auth keyword arguments
                                      user:     nil,
                                      password: nil,
                                      # Header keyword arguments
                                      headers:    nil,
                                      user_agent: nil,
                                      cookie:     nil,
                                      # request body keyword arguments
                                      body:      nil,
                                      form_data: nil)
            request_class = METHODS.fetch(method) do
              raise(ArgumentError,"unknown HTTP request method: #{method.inspect}")
            end

            request = request_class.new(
              request_uri(path, query: query, query_params: query_params),
              headers
            )

            if user
              user     = user.to_s
              password = password.to_s if password

              request.basic_auth(user,password)
            end

            if user_agent
              request['User-Agent'] = case user_agent
                                      when Symbol then UserAgents[user_agent]
                                      else             user_agent
                                      end
            end

            if cookie && !cookie.empty?
              request['Cookie'] = case cookie
                                  when Hash then HTTP::Cookie.new(cookie).to_s
                                  else           cookie.to_s
                                  end
            end

            if form_data
              case form_data
              when String
                request.content_type = 'application/x-www-form-urlencoded'
                request.body         = form_data
              else
                request.form_data    = form_data
              end
            elsif body
              case body
              when IO, StringIO then request.body_stream = body
              else                   request.body        = body
              end
            end

            return request
          end
        end
      end
    end
  end
end