# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true

require 'contrast/agent/reporting/reporting_events/reportable_hash'

module Contrast
  module Agent
    module Reporting
      # This is the new FindingRequest class which will include all the needed information for the new reporting system
      # to relay this information in the Finding/Trace messages. These requests are used by TeamServer to construct the
      # HTTP information for the assess feature. They represent the literal request made that resulted in the
      # vulnerability being triggered.
      class FindingRequest < Contrast::Agent::Reporting::ReportableHash
        OMITTED_BODY = '{{body-omitted-by-contrast}}'

        # @return [String] the body of this request
        attr_accessor :body
        # @return [Hash<String,Array<String>>] the headers of this request
        attr_accessor :headers
        # @return [String] the HTTP verb of this request
        attr_reader :method
        # @return [Hash<String,Array<String>>] the parameters of this request
        attr_reader :parameters
        # @return [Integer] the port to which this request connected
        attr_reader :port
        # @return [String] the HTTP(S) protocol of this request
        attr_reader :protocol
        # @return [String] the query string of this request
        attr_accessor :query_string
        # @return [String] the url, including path and script, of this request
        attr_reader :uri
        # @return [String] the HTTP version of this request
        attr_reader :version
        # @return [String]
        attr_reader :ip
        # @return [String] Byte representation of the body
        attr_accessor :body_binary
        # @return [Hash]
        attr_reader :cookies

        class << self
          # @param request [Contrast::Agent::Request]
          # @return [Contrast::Agent::Reporting::FindingRequest, nil]
          def convert request
            return unless request

            report = new
            report.attach_data(request)
            report
          end
        end

        # Parse the data from a Contrast::Agent::Request to attach what is required for reporting to TeamServer to this
        # Contrast::Agent::Reporting::FindingRequest
        #
        # @param request [Contrast::Agent::Request]
        def attach_data request
          @body = request.body
          @headers = {}
          extract_headers(request)
          @method = request.request_method
          @parameters = {}
          request.parameters.each_pair { |key, value| @parameters[key] = Array(value) }
          @port = request.port || 0
          @protocol = request.scheme
          @query_string = request.query_string
          @uri = request.normalized_uri
          @version = request.version
          @ip = request.ip || ''
          @body_binary = if omit_body?(request)
                           OMITTED_BODY
                         else
                           Contrast::Utils::StringUtils.force_utf8(request.body)
                         end
          @cookies = {}
          @cookies = request.cookies unless request.cookies.empty?
        end

        # Convert the instance variables on the class, and other information, into the identifiers required for
        # TeamServer to process the JSON form of this message.
        #
        # @return [Hash]
        # @raise [ArgumentError]
        def to_controlled_hash
          validate
          {
              body: body,
              headers: headers,
              method: method, # rubocop:disable Security/Object/Method
              parameters: parameters,
              port: port || 0,
              protocol: protocol,
              queryString: query_string,
              uri: uri,
              version: version
          }
        end

        def omit_body? request
          return true if ::Contrast::AGENT.omit_body?
          return false if request.document_type != :NORMAL

          request.media_type&.include?('multipart/form-data')
        end

        def validate
          unless method && !method.empty? # rubocop:disable Security/Object/Method
            raise(ArgumentError, "#{ self } did not have a proper method. Unable to continue.")
          end
          raise(ArgumentError, "#{ self } did not have a proper uri. Unable to continue.") unless uri && !uri.empty?
        end

        # @param request [Contrast::Agent::Request]
        def extract_headers request
          request.headers.each_pair do |key, value|
            # We need to change from the uppercase _ format to capitalized - format.
            header = key.split('_')
            header.each(&:capitalize!)
            header = header.join('-')
            headers[header] = value.split
          end
        end
      end
    end
  end
end