Repository: urllib3/urllib3

Issue:
Error using https and ipv6: InvalidURL("nonnumeric port: '4f7b'",)
To reproduce:

``` Python
import urllib3
p = urllib3.connection_from_url('https://[2001:0:53aa:64c:104c:2c10:2bef:4f7b]')
p.request('GET', '/')
```

produces:

``` Python
Traceback (most recent call last):
  File "/usr/lib/python3.4/http/client.py", line 771, in _set_hostport
    port = int(host[i+1:])
ValueError: invalid literal for int() with base 10: '4f7b'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.4/dist-packages/urllib3/connectionpool.py", line 548, in urlopen
    conn = self._get_conn(timeout=pool_timeout)
  File "/usr/local/lib/python3.4/dist-packages/urllib3/connectionpool.py", line 250, in _get_conn
    return conn or self._new_conn()
  File "/usr/local/lib/python3.4/dist-packages/urllib3/connectionpool.py", line 770, in _new_conn
    strict=self.strict, **self.conn_kw)
  File "/usr/local/lib/python3.4/dist-packages/urllib3/connection.py", line 171, in __init__
    timeout=timeout, **kw)
  File "/usr/local/lib/python3.4/dist-packages/urllib3/connection.py", line 119, in __init__
    _HTTPConnection.__init__(self, *args, **kw)
  File "/usr/lib/python3.4/http/client.py", line 750, in __init__
    self._set_hostport(host, port)
  File "/usr/lib/python3.4/http/client.py", line 776, in _set_hostport
    raise InvalidURL("nonnumeric port: '%s'" % host[i+1:])
http.client.InvalidURL: nonnumeric port: '4f7b'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.4/dist-packages/urllib3/request.py", line 68, in request
    **urlopen_kw)
  File "/usr/local/lib/python3.4/dist-packages/urllib3/request.py", line 89, in request_encode_url
    return self.urlopen(method, url, **extra_kw)
  File "/usr/local/lib/python3.4/dist-packages/urllib3/connectionpool.py", line 629, in urlopen
    release_conn=release_conn, **response_kw)
  File "/usr/local/lib/python3.4/dist-packages/urllib3/connectionpool.py", line 629, in urlopen
    release_conn=release_conn, **response_kw)
  File "/usr/local/lib/python3.4/dist-packages/urllib3/connectionpool.py", line 629, in urlopen
    release_conn=release_conn, **response_kw)
  File "/usr/local/lib/python3.4/dist-packages/urllib3/connectionpool.py", line 609, in urlopen
    _stacktrace=sys.exc_info()[2])
  File "/usr/local/lib/python3.4/dist-packages/urllib3/util/retry.py", line 271, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='2001:0:53aa:64c:104c:2c10:2bef:4f7b', port=None): Max retries exceeded with url: / (Caused by ProtocolError('Connection aborted.', InvalidURL("nonnumeric port: '4f7b'",)))
```

Using `https://[2001:0:53aa:64c:104c:2c10:2bef:4f7b]:443` instead is a workaround.



Discussion:
Interestingly, this seems to be an artefact of the way `http.client` behaves. It looks like the square brackets are mandatory:

``` python
Python 3.4.3 (default, Sep 15 2015, 12:08:21) 
[GCC 4.2.1 Compatible Apple LLVM 6.1.0 (clang-602.0.53)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import http.client
>>> c = http.client.HTTPConnection('[2001:0:53aa:64c:104c:2c10:2bef:4f7b]')
>>> c = http.client.HTTPConnection('2001:0:53aa:64c:104c:2c10:2bef:4f7b')
Traceback (most recent call last):
  File "/Users/cory/.pyenv/versions/3.4.3/lib/python3.4/http/client.py", line 786, in _get_hostport
    port = int(host[i+1:])
ValueError: invalid literal for int() with base 10: '4f7b'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/cory/.pyenv/versions/3.4.3/lib/python3.4/http/client.py", line 750, in __init__
    (self.host, self.port) = self._get_hostport(host, port)
  File "/Users/cory/.pyenv/versions/3.4.3/lib/python3.4/http/client.py", line 791, in _get_hostport
    raise InvalidURL("nonnumeric port: '%s'" % host[i+1:])
http.client.InvalidURL: nonnumeric port: '4f7b'
```

The same problem exists on all versions of Python that I tested, including 3.5 and 2.7.

Interestingly, this seems to be in opposition to the comment I can find on `connectionpool.py` line 71:

```
        # httplib doesn't like it when we include brackets in ipv6 addresses
        self.host = host.strip('[]')
```

That comment went in as part of 208ba74c796116bafc95d46dfcf4325449643ab6.

And that came from #203.

Wow, our users clearly don't use raw IPv6 addresses very often, or I'd have expected we'd see this sooner.

Regardless, I have no idea what caused @t-8ch to believe that the stdlib didn't like the square brackets, beyond the fact that it lead to an incorrect Host header in Python 2.6. That appears to have been fixed by [this Python bug](https://bugs.python.org/issue5111) in 2.7 but never backported to 2.6. Let me see if I can reproduce.

Nope, Python 2.6 doesn't like this either. I don't believe this was _ever_ right. Why haven't our tests caught this?

Oh, that's why: the existence of the port hides the failure. Our tests always use a port because we're always testing against non-80 and non-443 ports, so we have to explicitly add them. Bizarre.

The final piece of the puzzle is why it works when you add a port, and that's because it goes down a different path of httplib. If you provide `port=None` to httplib then it tries to parse the URL for a port and screws it up because we didn't provide the square brackets (note that we can't parse without them either).

I feel like the correct fix here is simple to remove the stripping of square brackets, though I'd like to hear from @t-8ch first.

That was fast :)

@thomascellerier We aim to please! Also it's raining cats and dogs outside so I have nothing better to do. :sunglasses: 

So as discussed above, I believe the fix here is simple, but I want to get clarification from the original developer if we can rustle him up.


Resolving patch:
diff --git a/urllib3/connectionpool.py b/urllib3/connectionpool.py
--- a/urllib3/connectionpool.py
+++ b/urllib3/connectionpool.py
@@ -68,8 +68,7 @@ def __init__(self, host, port=None):
         if not host:
             raise LocationValueError("No host specified.")
 
-        # httplib doesn't like it when we include brackets in ipv6 addresses
-        self.host = host.strip('[]')
+        self.host = host
         self.port = port
 
     def __str__(self):


Test patch: