diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index 6a0f638..779c855 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -1570,7 +1570,23 @@ def find_proxy(env=ENV) def self.use_proxy?(hostname, addr, port, no_proxy) # :nodoc: hostname = hostname.downcase dothostname = ".#{hostname}" - no_proxy.scan(/([^:,\s]+)(?::(\d+))?/) {|p_host, p_port| + no_proxy.split(/[,\s]+/).each {|entry| + next if entry.empty? + # Bracketed IPv6: [addr] or [addr]:port + if entry =~ /\A\[([^\]]+)\](?::(\d+))?\z/ + p_port = $2 # read $2 before sub() clobbers global match variables + p_host = $1.sub(/%.*\z/, '') # strip zone ID (e.g. [::1%eth0] -> ::1) + # Bare IPv6: address contains more than one colon (e.g. 2001:db8::1 or 2001:db8::/32) + elsif entry.count(':') > 1 + p_host = entry.sub(/%.*\z/, '') # strip zone ID (e.g. ::1%eth0 -> ::1) + p_port = nil + # IPv4 or hostname with optional port (trailing colon treated as no port) + elsif entry =~ /\A([^:]+)(?::(\d+)?)?\z/ + p_host = $1 + p_port = ($2 && !$2.empty?) ? $2 : nil + else + next + end if !p_port || port == p_port.to_i if p_host.start_with?('.') return false if hostname.end_with?(p_host.downcase) @@ -1581,7 +1597,6 @@ def self.use_proxy?(hostname, addr, port, no_proxy) # :nodoc: begin return false if IPAddr.new(p_host).include?(addr) rescue IPAddr::InvalidAddressError - next end end end diff --git a/test/uri/test_generic.rb b/test/uri/test_generic.rb index 94eea71..d12880f 100644 --- a/test/uri/test_generic.rb +++ b/test/uri/test_generic.rb @@ -1020,6 +1020,26 @@ def test_use_proxy_p ['127.0.0.1', '127.0.0.1', 80, '10.224.0.0/22', true], ['10.224.1.1', '10.224.1.1', 80, '10.224.1.1', false], ['10.224.1.1', '10.224.1.1', 80, '10.224.0.0/22', false], + # IPv6: bare address in no_proxy + ['2001:db8::1', '2001:db8::1', 80, '2001:db8::1', false], + # IPv6: bracketed address in no_proxy + ['2001:db8::1', '2001:db8::1', 80, '[2001:db8::1]', false], + # IPv6: bracketed address with matching port + ['2001:db8::1', '2001:db8::1', 80, '[2001:db8::1]:80', false], + # IPv6: bracketed address with non-matching port + ['2001:db8::1', '2001:db8::1', 80, '[2001:db8::1]:443', true], + # IPv6: CIDR notation + ['2001:db8::1', '2001:db8::1', 80, '2001:db8::/32', false], + # IPv6: address not in no_proxy + ['2001:db8::2', '2001:db8::2', 80, '2001:db8::1', true], + # IPv6: multiple entries including IPv6 + ['2001:db8::1', '2001:db8::1', 80, 'example.com,2001:db8::1', false], + # IPv6: zone ID stripped before matching (::1%eth0 should match ::1) + ['::1', '::1', 80, '::1%eth0', false], + # IPv6: zone ID stripped from bracketed form too ([::1%eth0]:80 should match ::1 on port 80) + ['::1', '::1', 80, '[::1%eth0]:80', false], + # Trailing colon treated as no-port (regression guard) + ['example.com', nil, 80, 'example.com:', false], ].each do |hostname, addr, port, no_proxy, expected| assert_equal expected, URI::Generic.use_proxy?(hostname, addr, port, no_proxy), "use_proxy?('#{hostname}', '#{addr}', #{port}, '#{no_proxy}')"