Ensure types are maintained on URL components
MODIFY
CHANGES.rst
CHANGES.rst
@@ -6,7 +6,7 @@ dev (master)
* Implemented a more efficient ``HTTPResponse.__iter__()`` method. (Issue #1483)
-* Upgraded ``urllib3.utils.parse_url()`` to be RFC 3986 compliant. (Issue #)
+* Upgraded ``urllib3.utils.parse_url()`` to be RFC 3986 compliant. (Pull #1487)
* ... [Short description of non-trivial change.] (Issue #)

MODIFY
src/urllib3/util/url.py
src/urllib3/util/url.py
@@ -160,6 +160,8 @@ def parse_url(url):
# Empty
return Url()
+ is_string = not isinstance(url, six.binary_type)
+
# RFC 3986 doesn't like URLs that have a host but don't start
# with a scheme and we support URLs like that so we need to
# detect that problem and add an empty scheme indication.
@@ -190,20 +192,24 @@ def parse_url(url):
path = None
# Ensure that each part of the URL is a `str` for
- # backwards compatbility.
- def to_str(x):
- if six.PY2 and isinstance(x, six.string_types):
+ # backwards compatibility.
+ def to_input_type(x):
+ if x is None:
+ return None
+ elif is_string and isinstance(x, six.binary_type):
+ return x.decode('utf-8')
+ elif not is_string and not isinstance(x, six.binary_type):
return x.encode('utf-8')
return x
return Url(
- scheme=to_str(parse_result.scheme),
- auth=to_str(parse_result.userinfo),
- host=to_str(parse_result.hostname),
+ scheme=to_input_type(parse_result.scheme),
+ auth=to_input_type(parse_result.userinfo),
+ host=to_input_type(parse_result.hostname),
port=parse_result.port,
- path=to_str(path),
- query=to_str(parse_result.query),
- fragment=to_str(parse_result.fragment)
+ path=to_input_type(path),
+ query=to_input_type(parse_result.query),
+ fragment=to_input_type(parse_result.fragment)
)

MODIFY
test/test_util.py
test/test_util.py
@@ -44,6 +44,8 @@ from urllib3.packages import six
from . import clear_warnings
+from test import onlyPy3, onlyPy2
+
# This number represents a time in seconds, it doesn't mean anything in
# isolation. Setting to a high-ish value to avoid conflicts with the smaller
# numbers used for timeouts
@@ -174,7 +176,17 @@ class TestUtil(object):
('http://foo:bar@baz@localhost/', Url('http',
auth='foo:bar@baz',
host='localhost',
- path='/'))
+ path='/')),
+
+ # Unicode type (Python 2.x)
+ (u'http://foo:bar@localhost/', Url(u'http',
+ auth=u'foo:bar',
+ host=u'localhost',
+ path=u'/')),
+ ('http://foo:bar@localhost/', Url('http',
+ auth='foo:bar',
+ host='localhost',
+ path='/')),
]
non_round_tripping_parse_url_host_map = [
@@ -264,6 +276,29 @@ class TestUtil(object):
else:
assert parse_url(url) == expected_url
+ @onlyPy2
+ def test_parse_url_bytes_to_str_python_2(self):
+ url = parse_url(b"https://www.google.com/")
+ assert url == Url('https', host='www.google.com', path='/')
+
+ assert isinstance(url.scheme, str)
+ assert isinstance(url.host, str)
+ assert isinstance(url.path, str)
+
+ @onlyPy2
+ def test_parse_url_unicode_python_2(self):
+ url = parse_url(u"https://www.google.com/")
+ assert url == Url(u'https', host=u'www.google.com', path=u'/')
+
+ assert isinstance(url.scheme, six.text_type)
+ assert isinstance(url.host, six.text_type)
+ assert isinstance(url.path, six.text_type)
+
+ @onlyPy3
+ def test_parse_url_bytes_type_error_python_3(self):
+ with pytest.raises(TypeError):
+ parse_url(b"https://www.google.com/")
+
@pytest.mark.parametrize('kwargs, expected', [
({'accept_encoding': True},
{'accept-encoding': 'gzip,deflate'}),

Ensure types are maintained on URL components (#1509)