Skip to content

Commit

Permalink
Correctly handle ipv6 addresses as a part of URL (#1349)
Browse files Browse the repository at this point in the history
* Correctly handle ipv6 addresses as a host

* Fixed typo

* Added an extra rfc reference

* Update tests/models/test_url.py

* Update tests/models/test_url.py
  • Loading branch information
cdeler committed Oct 8, 2020
1 parent dbbcf43 commit 7fda99f
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 1 deletion.
21 changes: 20 additions & 1 deletion httpx/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ def __init__(
raw_scheme, raw_host, port, raw_path = url
scheme = raw_scheme.decode("ascii")
host = raw_host.decode("ascii")
if host and ":" in host and host[0] != "[":
# it's an IPv6 address, so it should be enclosed in "[" and "]"
# ref: https://tools.ietf.org/html/rfc2732#section-2
# ref: https://tools.ietf.org/html/rfc3986#section-3.2.2
host = f"[{host}]"
port_str = "" if port is None else f":{port}"
path = raw_path.decode("ascii")
url = f"{scheme}://{host}{port_str}{path}"
Expand Down Expand Up @@ -186,8 +191,17 @@ def host(self) -> str:
url = httpx.URL("http://中国.icom.museum")
assert url.host == "xn--fiqs8s.icom.museum"
url = httpx.URL("https://[::ffff:192.168.0.1]")
assert url.host == "::ffff:192.168.0.1"
"""
return self._uri_reference.host or ""
host: str = self._uri_reference.host

if host and ":" in host and host[0] == "[":
# it's an IPv6 address
host = host.lstrip("[").rstrip("]")

return host or ""

@property
def port(self) -> typing.Optional[int]:
Expand Down Expand Up @@ -336,6 +350,11 @@ def copy_with(self, **kwargs: typing.Any) -> "URL":
# Consolidate host and port into netloc.
host = kwargs.pop("host", self.host) or ""
port = kwargs.pop("port", self.port)

if host and ":" in host and host[0] != "[":
# it's an IPv6 address, so it should be hidden under bracket
host = f"[{host}]"

kwargs["netloc"] = f"{host}:{port}" if port is not None else host

if "userinfo" in kwargs or "netloc" in kwargs:
Expand Down
34 changes: 34 additions & 0 deletions tests/models/test_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,37 @@ def test_url_with_url_encoded_path():
assert url.path == "/path to somewhere"
assert url.query == b""
assert url.raw_path == b"/path%20to%20somewhere"


def test_ipv6_url():
url = httpx.URL("http://[::ffff:192.168.0.1]:5678/")

assert url.host == "::ffff:192.168.0.1"
assert url.netloc == "[::ffff:192.168.0.1]:5678"


@pytest.mark.parametrize(
"url_str",
[
"http://127.0.0.1:1234",
"http://example.com:1234",
"http://[::ffff:127.0.0.1]:1234",
],
)
@pytest.mark.parametrize("new_host", ["[::ffff:192.168.0.1]", "::ffff:192.168.0.1"])
def test_ipv6_url_copy_with_host(url_str, new_host):
url = httpx.URL(url_str).copy_with(host=new_host)

assert url.host == "::ffff:192.168.0.1"
assert url.netloc == "[::ffff:192.168.0.1]:1234"
assert str(url) == "http://[::ffff:192.168.0.1]:1234"


@pytest.mark.parametrize("host", [b"[::ffff:192.168.0.1]", b"::ffff:192.168.0.1"])
def test_ipv6_url_from_raw_url(host):
raw_url = (b"https", host, 443, b"/")
url = httpx.URL(raw_url)

assert url.host == "::ffff:192.168.0.1"
assert url.netloc == "[::ffff:192.168.0.1]:443"
assert str(url) == "https://[::ffff:192.168.0.1]:443/"

0 comments on commit 7fda99f

Please sign in to comment.