# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Utils # used in Contrast::Agent::Response module ResponseUtils private HTTP_PREFIX = /^[Hh][Tt][Tt][Pp][_-]/i.cs__freeze # Given some holder of the content of the response's body, extract that # content and return it as a String # # @param body [String, Rack::File, Rack::BodyProxy, # ActionDispatch::Response::RackBody, Rack::Response] Something that # holds, wraps, or is the body of the Response # @return [nil, String] the content of the body def extract_body body return unless body return if body_is_file?(body) return if body_is_sprockets?(body) return handle_rack_body_proxy(body) if body.is_a?(Rack::BodyProxy) return extract_body(body.body) if sub_extractable?(body) return enumerable_text_from(body) if Contrast::Utils::DuckUtils.quacks_to?(body, :each) # https://stackoverflow.com/questions/15654676/how-to-convert-activesupportsafebuffer-to-string return body.to_str if body.is_a?(ActionView::OutputBuffer) read_or_string(body) end def sub_extractable? body (defined?(ActionDispatch::Response::RackBody) && body.is_a?(ActionDispatch::Response::RackBody)) || body.is_a?(Rack::Response) end def enumerable_text_from body entries = body.map { |entry| read_or_string(entry) } entries.compact! entries.join(Contrast::Utils::ObjectShare::NEW_LINE) end def handle_rack_body_proxy body next_body = body.instance_variable_get(:@body) case next_body when Array extract_body(next_body[0]) else extract_body(next_body) end end def read_or_string obj return unless obj if Contrast::Utils::DuckUtils.quacks_to?(obj, :read) tmp = obj.read obj.rewind tmp else obj.to_s end end # After Rack 3.1 gets live - line 88 (Rack::File) will be removed. # In 3.1 version, they drop the support for File class and will only support Files class # # @param body [String, Rack::File, Rack::BodyProxy, # ActionDispatch::Response::RackBody, Rack::Response] Something that # holds, wraps, or is the body of the Response def body_is_file? body return true if defined?(Rack::File) && (body.is_a?(Rack::File) || body.is_a?(Rack::File::Iterator)) return true if defined?(Rack::Files) && (body.is_a?(Rack::Files) || body.is_a?(Rack::Files::Iterator)) false end # Detects Rails' Sprockets asset pipeline objects passed as body. # Returns false if Sprockets is passed as body, the Agent does not # support Sprockets::Asset for body extraction. # # @param body [String, Rack::File, Rack::BodyProxy] def body_is_sprockets? body return body.cs__is_a?(Sprockets::Asset) if defined?(Sprockets) && defined?(Sprockets::Asset) false end end end end