Skip to content

Commit

Permalink
Use ipaddress to detect valid and invalid IPs
Browse files Browse the repository at this point in the history
  • Loading branch information
azmeuk committed Sep 24, 2022
1 parent 7748462 commit 38587f3
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 19 deletions.
47 changes: 38 additions & 9 deletions furl/furl.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import re
import abc
import ipaddress
import warnings
from copy import deepcopy
from posixpath import normpath
Expand Down Expand Up @@ -241,6 +242,37 @@ def is_valid_host(hostname):
return '' not in toks # Adjacent periods aren't allowed.


def is_valid_ipv4(ip):
if isinstance(ip, six.binary_type):
ip = ip.decode()

try:
ipaddress.IPv4Address(ip)
return True
except ValueError:
return False


def is_valid_ipv6(ip):
if isinstance(ip, six.binary_type):
ip = ip.decode()

# ipaddress handle IPs without brackets
if (
callable_attr(ip, 'startswith')
and callable_attr(ip, 'endswith')
and ip.startswith("[")
and ip.endswith("]")
):
ip = ip[1:-1]

try:
ipaddress.IPv6Address(ip)
return True
except ValueError:
return False


def get_scheme(url):
if url.startswith(':'):
return ''
Expand Down Expand Up @@ -1434,15 +1466,12 @@ def host(self, host):
"""
Raises: ValueError on invalid host or malformed IPv6 address.
"""
# Invalid IPv6 literal.
urllib.parse.urlsplit('http://%s/' % host) # Raises ValueError.

# Invalid host string.
resembles_ipv6_literal = (
host is not None and lget(host, 0) == '[' and ':' in host and
lget(host, -1) == ']')
if (host is not None and not resembles_ipv6_literal and
not is_valid_host(host)):
if (
host
and not is_valid_host(host)
and not is_valid_ipv4(host)
and not is_valid_ipv6(host)
):
errmsg = (
"Invalid host '%s'. Host strings must have at least one "
"non-period character, can't contain any of '%s', and can't "
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def run_tests(self):
install_requires=[
'six>=1.8.0',
'orderedmultidict>=1.0.1',
'ipaddress>=1.0.23; python_version < "3.3"',
],
cmdclass={
'test': RunTests,
Expand Down
21 changes: 11 additions & 10 deletions tests/test_furl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1655,10 +1655,10 @@ def test_hosts(self):
# addresses.
f = furl.furl('http://1.2.3.4.5.6/')

# Invalid, but well-formed, IPv6 addresses shouldn't raise an
# exception because urlparse.urlsplit() doesn't raise an
# exception on invalid IPv6 addresses.
furl.furl('http://[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]/')
# Invalid, but well-formed, IPv6 addresses should raise an
# exception.
with self.assertRaises(ValueError):
furl.furl('http://[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]/')

# Malformed IPv6 should raise an exception because urlparse.urlsplit()
# raises an exception on malformed IPv6 addresses.
Expand All @@ -1684,12 +1684,17 @@ def test_netloc(self):
assert f.host == '1.2.3.4.5.6'
assert f.port == 999

netloc = '[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]:888'
netloc = '[1:2:3:4:5:6:7:8]:888'
f.netloc = netloc
assert f.netloc == netloc
assert f.host == '[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]'
assert f.host == '[1:2:3:4:5:6:7:8]'
assert f.port == 888

# Well-formed but invalid IPv6 should raise an exception
netloc = '[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]:888'
with self.assertRaises(ValueError):
f.netloc = netloc

# Malformed IPv6 should raise an exception because
# urlparse.urlsplit() raises an exception
with self.assertRaises(ValueError):
Expand All @@ -1703,10 +1708,6 @@ def test_netloc(self):
with self.assertRaises(ValueError):
f.netloc = 'pump2pump.org:777777777777'

# No side effects.
assert f.host == '[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]'
assert f.port == 888

# Empty netloc.
f = furl.furl('//')
assert f.scheme is None and f.netloc == '' and f.url == '//'
Expand Down

0 comments on commit 38587f3

Please sign in to comment.