# 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 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 # A form of the Rack::Response which we can report to the Service to be sent to TeamServer for processing. B/c # the response can change, we can't memoize this :( # # @return [Contrast::Api::Dtm::HttpResponse] def dtm context_response = Contrast::Api::Dtm::HttpResponse.new context_response.response_code = response_code.to_i headers&.each_pair do |key, value| append_pair(context_response.normalized_response_headers, key, value) end context_response.response_body_binary = Contrast::Utils::StringUtils.force_utf8(body) doc_type = document_type context_response.document_type = doc_type if doc_type context_response 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