8000 Reduce overhead to check if a host is an IP Address by bdraco · Pull Request #9095 · aio-libs/aiohttp · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Reduce overhead to check if a host is an IP Address #9095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/9095.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improved performance of checking if a host is an IP Address -- by :user:`bdraco`.
66 changes: 36 additions & 30 deletions aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
List,
Mapping,
Optional,
Pattern,
Protocol,
Tuple,
Type,
Expand Down Expand Up @@ -484,44 +483,51 @@ def __set__(self, inst: _TSelf[_T], value: _T) -> None:
except ImportError:
pass

_ipv4_pattern = (
r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}"
r"(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
)
_ipv6_pattern = (
r"^(?:(?:(?:[A-F0-9]{1,4}:){6}|(?=(?:[A-F0-9]{0,4}:){0,6}"
r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}$)(([0-9A-F]{1,4}:){0,5}|:)"
r"((:[0-9A-F]{1,4}){1,5}:|:)|::(?:[A-F0-9]{1,4}:){5})"
r"(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}"
r"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])|(?:[A-F0-9]{1,4}:){7}"
r"[A-F0-9]{1,4}|(?=(?:[A-F0-9]{0,4}:){0,7}[A-F0-9]{0,4}$)"
r"(([0-9A-F]{1,4}:){1,7}|:)((:[0-9A-F]{1,4}){1,7}|:)|(?:[A-F0-9]{1,4}:){7}"
r":|:(:[A-F0-9]{1,4}){7})$"
)
_ipv4_regex = re.compile(_ipv4_pattern)
_ipv6_regex = re.compile(_ipv6_pattern, flags=re.IGNORECASE)
_ipv4_regexb = re.compile(_ipv4_pattern.encode("ascii"))
_ipv6_regexb = re.compile(_ipv6_pattern.encode("ascii"), flags=re.IGNORECASE)

def is_ipv4_address(host: Optional[Union[str, bytes]]) -> bool:
"""Check if host looks like an IPv4 address.

def _is_ip_address(
regex: Pattern[str], regexb: Pattern[bytes], host: Optional[Union[str, bytes]]
) -> bool:
if host is None:
This function does not validate that the format is correct, only that
the host is a str or bytes, and its all numeric.

This check is only meant as a heuristic to ensure that
a host is not a domain name.
"""
if not host:
return False
# For a host to be an ipv4 address, it must be all numeric.
if isinstance(host, str):
return bool(regex.match(host))
elif isinstance(host, (bytes, bytearray, memoryview)):
return bool(regexb.match(host))
else:
raise TypeError(f"{host} [{type(host)}] is not a str or bytes")
return host.replace(".", "").isdigit()
if isinstance(host, (bytes, bytearray, memoryview)):
return host.decode("ascii").replace(".", "").isdigit()
raise TypeError(f"{host} [{type(host)}] is not a str or bytes")


is_ipv4_address = functools.partial(_is_ip_address, _ipv4_regex, _ipv4_regexb)
is_ipv6_address = functools.partial(_is_ip_address, _ipv6_regex, _ipv6_regexb)
def is_ipv6_address(host: Optional[Union[str, bytes]]) -> bool:
"""Check if host looks like an IPv6 address.

This function does not validate that the format is correct, only that
the host contains a colon and that it is a str or bytes.

This check is only meant as a heuristic to ensure that
a host is not a domain name.
"""
if not host:
return False
# The host must contain a colon to be an IPv6 address.
if isinstance(host, str):
return ":" in host
if isinstance(host, (bytes, bytearray, memoryview)):
return b":" in host
raise TypeError(f"{host} [{type(host)}] is not a str or bytes")


def is_ip_address(host: Optional[Union[str, bytes, bytearray, memoryview]]) -> bool:
"""Check if host looks like an IP Address.

This check is only meant as a heuristic to ensure that
a host is not a domain name.
"""
return is_ipv4_address(host) or is_ipv6_address(host)


Expand Down
28 changes: 12 additions & 16 deletions tests/test_helpers.py
5D05
Original file line number Diff line number Diff line change
Expand Up @@ -286,14 +286,6 @@ def test_is_ip_address() -> None:
assert not helpers.is_ip_address("localhost")
assert not helpers.is_ip_address("www.example.com")

# Out of range
assert not helpers.is_ip_address("999.999.999.999")
# Contain a port
assert not helpers.is_ip_address("127.0.0.1:80")
assert not helpers.is_ip_address("[2001:db8:0:1]:80")
# Too many "::"
assert not helpers.is_ip_address("1200::AB00:1234::2552:7777:1313")


def test_is_ip_address_bytes() -> None:
assert helpers.is_ip_address(b"127.0.0.1")
Expand All @@ -304,14 +296,6 @@ def test_is_ip_address_bytes() -> None:
assert not helpers.is_ip_address(b"localhost")
assert not helpers.is_ip_address(b"www.example.com")

# Out of range
assert not helpers.is_ip_address(b"999.999.999.999")
# Contain a port
assert not helpers.is_ip_address(b"127.0.0.1:80")
assert not helpers.is_ip_address(b"[2001:db8:0:1]:80")
# Too many "::"
assert not helpers.is_ip_address(b"1200::AB00:1234::2552:7777:1313")


def test_ipv4_addresses() -> None:
ip_addresses = [
Expand Down Expand Up @@ -360,6 +344,18 @@ def test_is_ip_address_invalid_type() -> None:
with pytest.raises(TypeError):
helpers.is_ip_address(object()) # type: ignore[arg-type]

with pytest.raises(TypeError):
helpers.is_ipv4_address(123) # type: ignore[arg-type]

with pytest.raises(TypeError):
helpers.is_ipv4_address(object()) # type: ignore[arg-type]

with pytest.raises(TypeError):
helpers.is_ipv6_address(123) # type: ignore[arg-type]

with pytest.raises(TypeError):
helpers.is_ipv6_address(object()) # type: ignore[arg-type]


# ----------------------------------- TimeoutHandle -------------------

Expand Down
Loading
0