# Copyright (c) 2023 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