# frozen_string_literal: true class FinalDestination::HTTP < Net::HTTP def connect raise ArgumentError.new("address cannot be nil or empty") if @address.blank? original_open_timeout = @open_timeout return super if @ipaddr timeout_at = current_time + @open_timeout # This iteration through addresses would normally happen in Socket#tcp # We do it here because we're tightly controlling addresses rather than # handing Socket#tcp a hostname ips = FinalDestination::SSRFDetector.lookup_and_filter_ips(@address, timeout: @connect_timeout) ips.each_with_index do |ip, index| debug "[FinalDestination] Attempting connection to #{ip}..." self.ipaddr = ip remaining_time = timeout_at - current_time if remaining_time <= 0 raise Net::OpenTimeout.new("Operation timed out - FinalDestination::HTTP") end @open_timeout = remaining_time return super rescue OpenSSL::SSL::SSLError, SystemCallError, Net::OpenTimeout => e debug "[FinalDestination] Error connecting to #{ip}... #{e.message}" was_last_attempt = index == ips.length - 1 raise if was_last_attempt end ensure @open_timeout = original_open_timeout end private def current_time Process.clock_gettime(Process::CLOCK_MONOTONIC) end end