Skip to content

Commit

Permalink
Implement no_proxy feature (#658)
Browse files Browse the repository at this point in the history
* Implement no_proxy feature

* Handle prefixed and subdomains by improving proxy_allowed?
  • Loading branch information
somethingnew2-0 authored and iMacTia committed Feb 9, 2017
1 parent 6b3add2 commit dea7726
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 31 deletions.
43 changes: 37 additions & 6 deletions lib/faraday/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,15 @@ def initialize(url = nil, options = nil)
@params.update(options.params) if options.params
@headers.update(options.headers) if options.headers

@proxy = nil
@proxy = @no_proxy = nil
proxy(options.fetch(:proxy) {
uri = ENV['http_proxy']
if uri && !uri.empty?
uri = 'http://' + uri if uri !~ /^http/i
uri = 'http://' + uri unless uri.downcase.start_with?('http')
uri
end
}, options.fetch(:no_proxy) {
ENV['no_proxy']
})

yield(self) if block_given?
Expand Down Expand Up @@ -281,9 +283,12 @@ def in_parallel(manager = nil)
end

# Public: Gets or Sets the Hash proxy options.
def proxy(arg = nil)
return @proxy if arg.nil?
@proxy = ProxyOptions.from(arg)
def proxy(proxy = nil, no_proxy = nil)
return @proxy if proxy.nil?
@no_proxy = no_proxy
.scan(/(?!\.)([^:,\s]+)(?::(\d+))?/)
.map {|host, port| [host, port, /(\A|\.)#{Regexp.quote host}\z/i]} unless no_proxy.nil?
@proxy = ProxyOptions.from(proxy)
end

def_delegators :url_prefix, :scheme, :scheme=, :host, :host=, :port, :port=
Expand Down Expand Up @@ -371,6 +376,7 @@ def run_request(method, url, body, headers)
req.url(url) if url
req.headers.update(headers) if headers
req.body = body if body
req.options.merge!(:proxy => self.proxy) if proxy_allowed?(build_exclusive_url(req.path))
yield(req) if block_given?
end

Expand All @@ -384,11 +390,36 @@ def build_request(method)
Request.create(method) do |req|
req.params = self.params.dup
req.headers = self.headers.dup
req.options = self.options.merge(:proxy => self.proxy)
req.options = self.options
yield(req) if block_given?
end
end

def proxy_allowed?(url)
return true if @no_proxy.nil?
uri = Utils.URI(url)
@no_proxy.none? do |host, port, host_regex|
next false if (port && uri.port != port.to_i)
next true if host_regex =~ uri.host
if uri.hostname
require 'socket'
begin
addr = IPSocket.getaddress(uri.hostname)
next true if /\A127\.|\A::1\z/ =~ addr
rescue SocketError
end
next false unless addr
require 'ipaddr'
next true if
begin
IPAddr.new(host)
rescue
next false
end.include?(addr)
end
end
end

# Internal: Build an absolute URL based on url_prefix.
#
# url - A String or URI-like object
Expand Down
6 changes: 3 additions & 3 deletions lib/faraday/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ def clear
def merge(value)
dup.update(value)
end

# Public
def dup
self.class.from(self)
end

alias clone dup

# Public
def fetch(key, *args)
unless symbolized_key_set.include?(key.to_sym)
Expand Down Expand Up @@ -242,7 +242,7 @@ def self.from(value)
memoized(:password) { uri.password && Utils.unescape(uri.password) }
end

class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
class ConnectionOptions < Options.new(:request, :ssl, :proxy, :no_proxy, :builder, :url,
:parallel_manager, :params, :headers, :builder_class)

options :request => RequestOptions, :ssl => SSLOptions
Expand Down
9 changes: 9 additions & 0 deletions test/adapters/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,15 @@ def test_proxy
end
end

def test_no_proxy
proxy_uri = URI(ENV['LIVE_PROXY'])
conn = create_connection(:proxy => proxy_uri, :no_proxy => 'localhost')

res = conn.get '/echo'

assert_nil res['via']
end

def test_proxy_auth_fail
proxy_uri = URI(ENV['LIVE_PROXY'])
proxy_uri.password = 'WRONG'
Expand Down
114 changes: 97 additions & 17 deletions test/connection_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,23 @@ def teardown
Faraday.default_connection_options = nil
end

def with_env(key, proxy)
old_value = ENV.fetch(key, false)
ENV[key] = proxy
def with_env(new_env)
old_env = {}

new_env.each do |key, value|
old_env[key] = ENV.fetch(key, false)
ENV[key] = value
end

begin
yield
ensure
if old_value == false
ENV.delete key
else
ENV[key] = old_value
old_env.each do |key, value|
if value == false
ENV.delete key
else
ENV[key] = value
end
end
end
end
Expand Down Expand Up @@ -285,23 +292,23 @@ def test_build_exclusive_url_handles_uri_instances
end

def test_proxy_accepts_string
with_env 'http_proxy', "http://duncan.proxy.com:80" do
with_env 'http_proxy' => "http://duncan.proxy.com:80" do
conn = Faraday::Connection.new
conn.proxy 'http://proxy.com'
assert_equal 'proxy.com', conn.proxy.host
end
end

def test_proxy_accepts_uri
with_env 'http_proxy', "http://duncan.proxy.com:80" do
with_env 'http_proxy' => "http://duncan.proxy.com:80" do
conn = Faraday::Connection.new
conn.proxy URI.parse('http://proxy.com')
assert_equal 'proxy.com', conn.proxy.host
end
end

def test_proxy_accepts_hash_with_string_uri
with_env 'http_proxy', "http://duncan.proxy.com:80" do
with_env 'http_proxy' => "http://duncan.proxy.com:80" do
conn = Faraday::Connection.new
conn.proxy :uri => 'http://proxy.com', :user => 'rick'
assert_equal 'proxy.com', conn.proxy.host
Expand All @@ -310,7 +317,7 @@ def test_proxy_accepts_hash_with_string_uri
end

def test_proxy_accepts_hash
with_env 'http_proxy', "http://duncan.proxy.com:80" do
with_env 'http_proxy' => "http://duncan.proxy.com:80" do
conn = Faraday::Connection.new
conn.proxy :uri => URI.parse('http://proxy.com'), :user => 'rick'
assert_equal 'proxy.com', conn.proxy.host
Expand All @@ -319,44 +326,44 @@ def test_proxy_accepts_hash
end

def test_proxy_accepts_http_env
with_env 'http_proxy', "http://duncan.proxy.com:80" do
with_env 'http_proxy' => "http://duncan.proxy.com:80" do
conn = Faraday::Connection.new
assert_equal 'duncan.proxy.com', conn.proxy.host
end
end

def test_proxy_accepts_http_env_with_auth
with_env 'http_proxy', "http://a%40b:my%[email protected]:80" do
with_env 'http_proxy' => "http://a%40b:my%[email protected]:80" do
conn = Faraday::Connection.new
assert_equal 'a@b', conn.proxy.user
assert_equal 'my pass', conn.proxy.password
end
end

def test_proxy_accepts_env_without_scheme
with_env 'http_proxy', "localhost:8888" do
with_env 'http_proxy' => "localhost:8888" do
uri = Faraday::Connection.new.proxy[:uri]
assert_equal 'localhost', uri.host
assert_equal 8888, uri.port
end
end

def test_no_proxy_from_env
with_env 'http_proxy', nil do
with_env 'http_proxy' => nil do
conn = Faraday::Connection.new
assert_nil conn.proxy
end
end

def test_no_proxy_from_blank_env
with_env 'http_proxy', '' do
with_env 'http_proxy' => '' do
conn = Faraday::Connection.new
assert_nil conn.proxy
end
end

def test_proxy_doesnt_accept_uppercase_env
with_env 'HTTP_PROXY', "http://localhost:8888/" do
with_env 'HTTP_PROXY' => "http://localhost:8888/" do
conn = Faraday::Connection.new
assert_nil conn.proxy
end
Expand All @@ -369,6 +376,79 @@ def test_proxy_requires_uri
end
end

def test_proxy_allowed_when_url_in_no_proxy_list
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example.com' do
conn = Faraday::Connection.new
assert_equal conn.proxy_allowed?('http://example.com'), false
end
end

def test_proxy_allowed_when_prefixed_url_is_not_in_no_proxy_list
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example.com' do
conn = Faraday::Connection.new
assert_equal conn.proxy_allowed?('http://prefixedexample.com'), true
end
end

def test_proxy_allowed_when_subdomain_url_is_in_no_proxy_list
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example.com' do
conn = Faraday::Connection.new
assert_equal conn.proxy_allowed?('http://subdomain.example.com'), false
end
end

def test_proxy_allowed_when_url_not_in_no_proxy_list
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example2.com' do
conn = Faraday::Connection.new
assert_equal conn.proxy_allowed?('http://example.com'), true
end
end

def test_proxy_allowed_when_ip_address_is_not_in_no_proxy_list_but_url_is
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'localhost' do
conn = Faraday::Connection.new
assert_equal conn.proxy_allowed?('http://127.0.0.1'), false
end
end

def test_proxy_allowed_when_url_is_not_in_no_proxy_list_but_ip_address_is
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => '127.0.0.1' do
conn = Faraday::Connection.new
assert_equal conn.proxy_allowed?('http://localhost'), false
end
end

def test_proxy_allowed_in_multi_element_no_proxy_list
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example0.com,example.com,example1.com' do
conn = Faraday::Connection.new
assert_equal conn.proxy_allowed?('http://example0.com'), false
assert_equal conn.proxy_allowed?('http://example.com'), false
assert_equal conn.proxy_allowed?('http://example1.com'), false
assert_equal conn.proxy_allowed?('http://example2.com'), true
end
end

def test_proxy_allowed_when_ports_match_in_no_proxy_list
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example.com:7171' do
conn = Faraday::Connection.new
assert_equal conn.proxy_allowed?('http://example.com:7171'), false
end
end

def test_proxy_allowed_when_port_is_not_set_in_no_proxy_list
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example.com' do
conn = Faraday::Connection.new
assert_equal conn.proxy_allowed?('http://example.com:7171'), false
end
end

def test_proxy_allowed_when_ports_mismatch_in_no_proxy_list
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example.com:3000' do
conn = Faraday::Connection.new
assert_equal conn.proxy_allowed?('http://example.com:7171'), true
end
end

def test_dups_connection_object
conn = Faraday::Connection.new 'http://sushi.com/foo',
:ssl => { :verify => :none },
Expand Down
5 changes: 0 additions & 5 deletions test/env_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,6 @@ def test_request_create_stores_ssl_options
assert_equal false, env.ssl.verify
end

def test_request_create_stores_proxy_options
env = make_env
assert_equal 'proxy.com', env.request.proxy.host
end

def test_custom_members_are_retained
env = make_env
env[:foo] = "custom 1"
Expand Down

0 comments on commit dea7726

Please sign in to comment.