Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Could not handle an URI if its host part is an IPv6 address. #122

Closed
Teshootub7 opened this Issue · 2 comments

2 participants

@Teshootub7

Hi,

I noticed that HTTPClient could not handle an URI if its host part is an IPv6 address, e.g. "http://[::1]/".

% cat test.rb 
require 'httpclient'
clnt = HTTPClient.new
clnt.get "http://[::1]/"

% ruby test.rb 
/home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient/session.rb:802:in `initialize': getaddrinfo: Name or service not known (http://[::1]:80) (SocketError)
        from /home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient/session.rb:802:in `new'
        from /home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient/session.rb:802:in `create_socket'
        from /home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient/session.rb:751:in `block in connect'
        from /usr/lib/ruby/1.9.1/timeout.rb:68:in `timeout'
        from /usr/lib/ruby/1.9.1/timeout.rb:99:in `timeout'
        from /home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient/session.rb:750:in `connect'
        from /home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient/session.rb:608:in `query'
        from /home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient/session.rb:163:in `query'
        from /home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient.rb:1080:in `do_get_block'
        from /home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient.rb:884:in `block in do_request'
        from /home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient.rb:978:in `protect_keep_alive_disconnected'
        from /home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient.rb:883:in `do_request'
        from /home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient.rb:771:in `request'
        from /home/qw/.gem/ruby/1.9.1/gems/httpclient-2.3.0.1/lib/httpclient.rb:674:in `get'
        from test.rb:3:in `<main>'

After an investigation, I found that HTTPClient tries to connect to "[::1]" instead of "::1".
Technically, IPv6 address must be enclosed by square brackets when specified in URI.(RFC 3986) But these square brackets must be removed when used for socket operations.

I made a patch (testcase included) to solve this problem.

diff --git a/lib/httpclient/session.rb b/lib/httpclient/session.rb
index 8fa2bb4..26a635b 100644
--- a/lib/httpclient/session.rb
+++ b/lib/httpclient/session.rb
@@ -29,7 +29,7 @@ class HTTPClient
   class Site
     # Protocol scheme.
     attr_accessor :scheme
-    # Host String.
+    # Host String. Square brackets enclosing the IPv6 address are removed.
     attr_accessor :host
     # Port number.
     attr_accessor :port
@@ -38,7 +38,7 @@ class HTTPClient
     def initialize(uri = nil)
       if uri
         @scheme = uri.scheme
-        @host = uri.host
+        @host = uri.host.gsub(/^\[|\]$/,'')
         @port = uri.port.to_i
       else
         @scheme = 'tcp'
@@ -49,7 +49,8 @@ class HTTPClient

     # Returns address String.
     def addr
-      "#{@scheme}://#{@host}:#{@port.to_s}"
+      # Enclose by square brackets for IPv6 address.
+      "#{@scheme}://#{@host =~ /:/ ? "[#{@host}]" : @host}:#{@port.to_s}"
     end

     # Returns true is scheme, host and port are '=='
@@ -72,7 +73,7 @@ class HTTPClient

     # Returns true if scheme, host and port of the given URI matches with this.
     def match(uri)
-      (@scheme == uri.scheme) and (@host == uri.host) and (@port == uri.port.to_i)
+      (@scheme == uri.scheme) and (@host == uri.host.gsub(/^\[|\]$/,'')) and (@port == uri.port.to_i)
     end

     def inspect # :nodoc:
diff --git a/test/test_httpclient.rb b/test/test_httpclient.rb
index 27b7ac9..0ac225a 100644
--- a/test/test_httpclient.rb
+++ b/test/test_httpclient.rb
@@ -1557,6 +1557,30 @@ EOS
     end
   end

+  def test_ipv6literaladdress_in_uri
+    server = TCPServer.open('::1', 0) rescue return # Skip if IPv6 is unavailable.
+    server_thread = Thread.new {
+      Thread.abort_on_exception = true
+      sock = server.accept
+      while line = sock.gets
+        break if line.chomp.empty?
+      end
+      sock.write("HTTP/1.1 200 OK\r\n")
+      sock.write("Content-Length: 5\r\n")
+      sock.write("\r\n")
+      sock.write("12345")
+      sock.close
+    }
+    uri = "http://[::1]:#{server.addr[1]}/"
+    begin
+      assert_equal('12345', @client.get(uri).body)
+    ensure
+      server.close
+      server_thread.kill
+      server_thread.join
+    end
+  end
+
 private

   def check_query_get(query)

Thanks,

@nahi
Owner

Thank you very much for the patch! Based on your investigation, I found URI#hostname: https://github.com/ruby/ruby/blob/trunk/lib/uri/generic.rb#L242-245

I also found that addressable doesn't support hostname.

irb> URI.parse("http://[::1]/").hostname
=> "::1"
irb> Addressable::URI.parse("http://[::1]/").hostname
=> "[::1]"

I'll try to wrap it.

@nahi nahi closed this issue from a commit
@nahi Support IPv6 address in URI
Fixes #122.
fd34794
@nahi nahi closed this in fd34794
@Teshootub7

URI#hostname

Wow! I didn't know such convenient method is there. Thanks so much for letting me know. (^-^)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.