DEV: Allow webmock to intercept `FinalDestination::HTTP` requests (#20575)
This commit is contained in:
parent
a252022117
commit
500d0f6daf
|
@ -1,40 +1,47 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class FinalDestination::HTTP < Net::HTTP
|
class FinalDestination
|
||||||
def connect
|
module SSRFSafeNetHTTP
|
||||||
original_open_timeout = @open_timeout
|
def connect
|
||||||
return super if @ipaddr
|
original_open_timeout = @open_timeout
|
||||||
|
return super if @ipaddr
|
||||||
|
|
||||||
timeout_at = current_time + @open_timeout
|
timeout_at = current_time + @open_timeout
|
||||||
|
|
||||||
# This iteration through addresses would normally happen in Socket#tcp
|
# This iteration through addresses would normally happen in Socket#tcp
|
||||||
# We do it here because we're tightly controlling addresses rather than
|
# We do it here because we're tightly controlling addresses rather than
|
||||||
# handing Socket#tcp a hostname
|
# handing Socket#tcp a hostname
|
||||||
ips = FinalDestination::SSRFDetector.lookup_and_filter_ips(@address, timeout: @connect_timeout)
|
ips =
|
||||||
|
FinalDestination::SSRFDetector.lookup_and_filter_ips(@address, timeout: @connect_timeout)
|
||||||
|
|
||||||
ips.each_with_index do |ip, index|
|
ips.each_with_index do |ip, index|
|
||||||
debug "[FinalDestination] Attempting connection to #{ip}..."
|
debug "[FinalDestination] Attempting connection to #{ip}..."
|
||||||
self.ipaddr = ip
|
self.ipaddr = ip
|
||||||
|
|
||||||
remaining_time = timeout_at - current_time
|
remaining_time = timeout_at - current_time
|
||||||
if remaining_time <= 0
|
if remaining_time <= 0
|
||||||
raise Net::OpenTimeout.new("Operation timed out - FinalDestination::HTTP")
|
raise Net::OpenTimeout.new("Operation timed out - FinalDestination::HTTP")
|
||||||
|
end
|
||||||
|
|
||||||
|
@open_timeout = remaining_time
|
||||||
|
return super
|
||||||
|
rescue 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
|
end
|
||||||
|
ensure
|
||||||
@open_timeout = remaining_time
|
@open_timeout = original_open_timeout
|
||||||
return super
|
end
|
||||||
rescue SystemCallError, Net::OpenTimeout => e
|
|
||||||
debug "[FinalDestination] Error connecting to #{ip}... #{e.message}"
|
private
|
||||||
was_last_attempt = index == ips.length - 1
|
|
||||||
raise if was_last_attempt
|
def current_time
|
||||||
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||||
end
|
end
|
||||||
ensure
|
|
||||||
@open_timeout = original_open_timeout
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
class HTTP < ::Net::HTTP
|
||||||
|
include SSRFSafeNetHTTP
|
||||||
def current_time
|
|
||||||
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,15 @@ WebMock::HttpLibAdapterRegistry.instance.register(
|
||||||
|
|
||||||
def self.enable!
|
def self.enable!
|
||||||
FinalDestination.send(:remove_const, :HTTP)
|
FinalDestination.send(:remove_const, :HTTP)
|
||||||
FinalDestination.send(:const_set, :HTTP, Net::HTTP)
|
|
||||||
|
# At this point, `Net::HTTP` has already been patched by WebMock so we need to re-declare `FinalDestination::HTTP`
|
||||||
|
# but inherit from the patched `Net::HTTP` class. This is to allow requests made using `FinalDestination::HTTP` to be
|
||||||
|
# intercepted by WebMock.
|
||||||
|
FinalDestination.send(
|
||||||
|
:const_set,
|
||||||
|
:HTTP,
|
||||||
|
Class.new(Net::HTTP) { include FinalDestination::SSRFSafeNetHTTP },
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.disable!
|
def self.disable!
|
||||||
|
|
Loading…
Reference in New Issue