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

require 'resolv'
require 'timeout'

require 'contrast/utils/object_share'
require 'contrast/utils/string_utils'
require 'contrast/utils/hash_digest'
require 'contrast/components/logger'
require 'contrast/utils/response_utils'

module Contrast
  module Agent
    # This class is the Contrast representation of the Rack::Response object. It
    # provides access to the original Rack::Response object as well as extracts
    # data in a format that the Agent expects, caching those transformations in
    # order to avoid repeatedly creating Strings & thrashing GC.
    class Response
      include Contrast::Components::Logger::InstanceMethods
      include Contrast::Utils::ResponseUtils

      extend Forwardable

      # @return [Array, Rack::Response] The Rack Response passed by the application & middleware. It can be an Array
      #   in format [response_code, header_hash, response_body] or an instance of Rack::Response
      attr_reader :rack_response

      def initialize rack_response
        @rack_response = rack_response
        @is_array = !rack_response.is_a?(Rack::Response)
      end

      # The document type of the response, based on its Content-Type. Can be one of :JSON, :NORMAL, :XML, or nothing.
      #
      # @return [Symbol<:XML, :JSON, :NORMAL>, nil]
      def document_type
        case content_type
        when /xml/i
          :XML
        when /json/i
          :JSON
        when /html/i
          :NORMAL
        end
      end

      # The response code of this response
      #
      # @return [Integer]
      def response_code
        return unless rack_response

        @is_array ? rack_response[0].to_i : rack_response.status
      end

      # The headers of this response
      #
      # @return [Hash, nil]
      def headers
        return unless rack_response

        @is_array ? rack_response[1] : rack_response.headers
      end

      # The Content-Type of this response, as set in the headers hash under the key Rack::CONTENT_TYPE
      #
      # @return [String, nil]
      def content_type
        return unless rack_response

        @is_array ? headers[Rack::CONTENT_TYPE] : rack_response.content_type
      end

      # The response body can change during the request lifecycle, so we have to look it up every time we need it.
      # We should not extract it out as a variable here, or we'll miss those changes.
      #
      # @return [String, nil]
      def body
        return unless rack_response

        body_content = @is_array ? rack_response[2] : rack_response.body
        extract_body(body_content)
      end
    end
  end
end