Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ip-address handling to no_proxy-envvar #718

Merged
merged 2 commits into from Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -5,6 +5,7 @@ before_script:
- if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6';
fi
- rm -f Gemfile.lock >/dev/null
cache: bundler
language: ruby
rvm:
Expand Down
135 changes: 83 additions & 52 deletions lib/excon/connection.rb
Expand Up @@ -488,6 +488,47 @@ def raise_socket_error(error)
end
end

def proxy_match_host_port(host, port)
host_match = if host.is_a? IPAddr
begin
host.include? @data[:host]
rescue IPAddr::Error
false
end
else
/(^|\.)#{host}$/.match(@data[:host])
end
host_match && (port.nil? || port.to_i == @data[:port])
end

def proxy_from_env
if (no_proxy_env = ENV['no_proxy'] || ENV['NO_PROXY'])
no_proxy_list = no_proxy_env.scan(/\s*(?:\[([\dA-Fa-f:\/]+)\]|\*?\.?([^\s,:]+))(?::(\d+))?\s*/i).map { |e|
if e[0]
begin
[IPAddr.new(e[0]), e[2]]
rescue IPAddr::Error
nil
end
else
begin
[IPAddr.new(e[1]), e[2]]
rescue IPAddr::Error
[e[1], e[2]]
end
end
}.reject { |e| e.nil? || e[0].nil? }
end

unless no_proxy_env && no_proxy_list.index { |h| proxy_match_host_port(h[0], h[1]) }
if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
@data[:proxy] = ENV['https_proxy'] || ENV['HTTPS_PROXY']
elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
@data[:proxy] = ENV['http_proxy'] || ENV['HTTP_PROXY']
end
end
end

def setup_proxy
if @data[:disable_proxy]
if @data[:proxy]
Expand All @@ -496,64 +537,54 @@ def setup_proxy
return
end

unless @data[:scheme] == UNIX
if no_proxy_env = ENV["no_proxy"] || ENV["NO_PROXY"]
no_proxy_list = no_proxy_env.scan(/\*?\.?([^\s,:]+)(?::(\d+))?/i).map { |s| [s[0], s[1]] }
return if @data[:scheme] == UNIX

proxy_from_env

case @data[:proxy]
when nil
@data.delete(:proxy)
when ''
@data.delete(:proxy)
when Hash
# no processing needed
when String, URI
uri = @data[:proxy].is_a?(String) ? URI.parse(@data[:proxy]) : @data[:proxy]
@data[:proxy] = {
:host => uri.host,
:hostname => uri.hostname,
# path is only sensible for a Unix socket proxy
:path => uri.scheme == UNIX ? uri.path : nil,
:port => uri.port,
:scheme => uri.scheme,
}
if uri.password
@data[:proxy][:password] = uri.password
end

unless no_proxy_env && no_proxy_list.index { |h| /(^|\.)#{h[0]}$/.match(@data[:host]) && (h[1].nil? || h[1].to_i == @data[:port]) }
if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
@data[:proxy] = ENV['https_proxy'] || ENV['HTTPS_PROXY']
elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
@data[:proxy] = ENV['http_proxy'] || ENV['HTTP_PROXY']
end
if uri.user
@data[:proxy][:user] = uri.user
end

case @data[:proxy]
when nil
@data.delete(:proxy)
when ''
@data.delete(:proxy)
when Hash
# no processing needed
when String, URI
uri = @data[:proxy].is_a?(String) ? URI.parse(@data[:proxy]) : @data[:proxy]
@data[:proxy] = {
:host => uri.host,
:hostname => uri.hostname,
# path is only sensible for a Unix socket proxy
:path => uri.scheme == UNIX ? uri.path : nil,
:port => uri.port,
:scheme => uri.scheme,
}
if uri.password
@data[:proxy][:password] = uri.password
end
if uri.user
@data[:proxy][:user] = uri.user
end
if @data[:proxy][:scheme] == UNIX
if @data[:proxy][:host]
raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
"When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
end
else
unless uri.host && uri.port && uri.scheme
raise Excon::Errors::ProxyParse, "Proxy is invalid"
end
if @data[:proxy][:scheme] == UNIX
if @data[:proxy][:host]
raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
"When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
end
else
raise Excon::Errors::ProxyParse, "Proxy is invalid"
unless uri.host && uri.port && uri.scheme
raise Excon::Errors::ProxyParse, "Proxy is invalid"
end
end
else
raise Excon::Errors::ProxyParse, "Proxy is invalid"
end

if @data.has_key?(:proxy) && @data[:scheme] == 'http'
@data[:headers]['Proxy-Connection'] ||= 'Keep-Alive'
# https credentials happen in handshake
if @data[:proxy].has_key?(:user) || @data[:proxy].has_key?(:password)
user, pass = Utils.unescape_form(@data[:proxy][:user].to_s), Utils.unescape_form(@data[:proxy][:password].to_s)
auth = ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
@data[:headers]['Proxy-Authorization'] = 'Basic ' + auth
end
if @data.has_key?(:proxy) && @data[:scheme] == 'http'
@data[:headers]['Proxy-Connection'] ||= 'Keep-Alive'
# https credentials happen in handshake
if @data[:proxy].has_key?(:user) || @data[:proxy].has_key?(:password)
user, pass = Utils.unescape_form(@data[:proxy][:user].to_s), Utils.unescape_form(@data[:proxy][:password].to_s)
auth = ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
@data[:headers]['Proxy-Authorization'] = 'Basic ' + auth
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion tests/proxy_tests.rb
Expand Up @@ -151,7 +151,7 @@ def env_proxy_tests(env)
env = {
'http_proxy' => 'http://myproxy:8080',
'https_proxy' => 'http://mysecureproxy:8081',
'no_proxy' => 'noproxy, subdomain.noproxy2'
'no_proxy' => 'noproxy, subdomain.noproxy2, [fc00::1], 10.0.0.1'
}
tests('lowercase') { env_proxy_tests(env) }
upperenv = {}
Expand Down