From 8b714c3e3a3f53c8f3a49d6b326fa0ef17c72457 Mon Sep 17 00:00:00 2001 From: SimonIT Date: Tue, 15 Feb 2022 22:51:20 +0100 Subject: [PATCH 1/7] Improve IPv6 validation fixes #107 --- tests/test_ipv6.py | 13 ++++++++++++- validators/ip_address.py | 16 ++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/tests/test_ipv6.py b/tests/test_ipv6.py index 3f321404..70326470 100644 --- a/tests/test_ipv6.py +++ b/tests/test_ipv6.py @@ -5,8 +5,10 @@ @pytest.mark.parametrize(('address',), [ + ('::',), ('::1',), - ('dead:beef:0:0:0:0:42:1',), + ('1::',), + ('dead:beef:0:0:0:0000:42:1',), ('abcd:ef::42:1',), ('0:0:0:0:0:ffff:1.2.3.4',), ('::192.168.30.2',), @@ -21,6 +23,15 @@ def test_returns_true_on_valid_ipv6_address(address): ('abcd:1234::123::1',), ('1:2:3:4:5:6:7:8:9',), ('abcd::1ffff',), + ('1111:',), + (':8888',), + (':1.2.3.4',), + ('18:05',), + (':',), + (':1:2:',), + (':1:2::',), + ('::1:2::',), + ('8::1:2::9',), ]) def test_returns_failed_validation_on_invalid_ipv6_address(address): assert isinstance(ipv6(address), ValidationFailure) diff --git a/validators/ip_address.py b/validators/ip_address.py index ba36cdc6..a42e6946 100644 --- a/validators/ip_address.py +++ b/validators/ip_address.py @@ -4,7 +4,7 @@ @validator def ipv4(value): """ - Return whether or not given value is a valid IP version 4 address. + Return whether a given value is a valid IP version 4 address. This validator is based on `WTForms IPAddress validator`_ @@ -32,7 +32,7 @@ def ipv4(value): @validator def ipv4_cidr(value): """ - Return whether or not given value is a valid CIDR-notated IP version 4 + Return whether a given value is a valid CIDR-notated IP version 4 address range. This validator is based on RFC4632 3.1. @@ -57,7 +57,7 @@ def ipv4_cidr(value): @validator def ipv6(value): """ - Return whether or not given value is a valid IP version 6 address + Return whether a given value is a valid IP version 6 address (including IPv4-mapped IPv6 addresses). This validator is based on `WTForms IPAddress validator`_. @@ -112,9 +112,13 @@ def ipv6(value): if not 0 <= num <= 65536: return False - if count_blank < 2: + if count_blank == 0 and len(ipv6_groups) == max_groups: return True - elif count_blank == 2 and not ipv6_groups[0] and not ipv6_groups[1]: + elif count_blank == 1 and ipv6_groups[-1] and ipv6_groups[0]: + return True + elif count_blank == 2 and ((ipv6_groups[0] and not ipv6_groups[-1]) or (not ipv6_groups[0] and ipv6_groups[-1])): + return True + elif count_blank == 3 and len(ipv6_groups) == 3: return True return False @@ -122,7 +126,7 @@ def ipv6(value): @validator def ipv6_cidr(value): """ - Returns whether or not given value is a valid CIDR-notated IP version 6 + Returns whether a given value is a valid CIDR-notated IP version 6 address range. This validator is based on RFC4632 3.1. From 1d07d2b1479d1d06bfc54dcd5685a02b0f68bdaa Mon Sep 17 00:00:00 2001 From: SimonIT Date: Tue, 15 Feb 2022 23:12:03 +0100 Subject: [PATCH 2/7] Add some comments --- validators/ip_address.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/validators/ip_address.py b/validators/ip_address.py index a42e6946..b2044bb3 100644 --- a/validators/ip_address.py +++ b/validators/ip_address.py @@ -113,12 +113,16 @@ def ipv6(value): return False if count_blank == 0 and len(ipv6_groups) == max_groups: + # no :: -> must have size of max_groups return True elif count_blank == 1 and ipv6_groups[-1] and ipv6_groups[0]: + # one :: inside the address or prefix or suffix : -> filter least two cases return True elif count_blank == 2 and ((ipv6_groups[0] and not ipv6_groups[-1]) or (not ipv6_groups[0] and ipv6_groups[-1])): + # leading or trailing :: or : at end and begin -> filter last case return True elif count_blank == 3 and len(ipv6_groups) == 3: + # :: is the address -> filter everything else return True return False From 752e2e4b91765a2e10ac355949c51e5556bfa43b Mon Sep 17 00:00:00 2001 From: SimonIT Date: Sun, 20 Feb 2022 17:32:06 +0100 Subject: [PATCH 3/7] Fix ipv4 with double colon --- tests/test_ipv6.py | 1 + validators/ip_address.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_ipv6.py b/tests/test_ipv6.py index 70326470..e1307d30 100644 --- a/tests/test_ipv6.py +++ b/tests/test_ipv6.py @@ -12,6 +12,7 @@ ('abcd:ef::42:1',), ('0:0:0:0:0:ffff:1.2.3.4',), ('::192.168.30.2',), + ('0000:0000:0000:0000:0000::',), ]) def test_returns_true_on_valid_ipv6_address(address): assert ipv6(address) diff --git a/validators/ip_address.py b/validators/ip_address.py index b2044bb3..10ceb8c5 100644 --- a/validators/ip_address.py +++ b/validators/ip_address.py @@ -118,8 +118,10 @@ def ipv6(value): elif count_blank == 1 and ipv6_groups[-1] and ipv6_groups[0]: # one :: inside the address or prefix or suffix : -> filter least two cases return True - elif count_blank == 2 and ((ipv6_groups[0] and not ipv6_groups[-1]) or (not ipv6_groups[0] and ipv6_groups[-1])): + elif count_blank == 2 and ( + ((ipv6_groups[0] and not ipv6_groups[-1]) or (not ipv6_groups[0] and ipv6_groups[-1])) or ipv4_groups): # leading or trailing :: or : at end and begin -> filter last case + # Check if it has ipv4 groups because they get removed from the ipv6_groups return True elif count_blank == 3 and len(ipv6_groups) == 3: # :: is the address -> filter everything else From c9fe7211139b96ac4d531e276521caf5d1980db7 Mon Sep 17 00:00:00 2001 From: SimonIT Date: Sun, 20 Feb 2022 17:48:22 +0100 Subject: [PATCH 4/7] Fix falsy to long ipv6 result and detect to long parts --- tests/test_ipv6.py | 2 ++ validators/ip_address.py | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_ipv6.py b/tests/test_ipv6.py index e1307d30..cac0eb72 100644 --- a/tests/test_ipv6.py +++ b/tests/test_ipv6.py @@ -13,6 +13,7 @@ ('0:0:0:0:0:ffff:1.2.3.4',), ('::192.168.30.2',), ('0000:0000:0000:0000:0000::',), + ('0:a:b:c:d:e:f::',), ]) def test_returns_true_on_valid_ipv6_address(address): assert ipv6(address) @@ -33,6 +34,7 @@ def test_returns_true_on_valid_ipv6_address(address): (':1:2::',), ('::1:2::',), ('8::1:2::9',), + ('02001:0000:1234:0000:0000:C1C0:ABCD:0876',), ]) def test_returns_failed_validation_on_invalid_ipv6_address(address): assert isinstance(ipv6(address), ValidationFailure) diff --git a/validators/ip_address.py b/validators/ip_address.py index 10ceb8c5..469d76e8 100644 --- a/validators/ip_address.py +++ b/validators/ip_address.py @@ -95,10 +95,6 @@ def ipv6(value): else: ipv4_groups = [] - max_groups = 6 if ipv4_groups else 8 - if len(ipv6_groups) > max_groups: - return False - count_blank = 0 for part in ipv6_groups: if not part: @@ -109,9 +105,10 @@ def ipv6(value): except ValueError: return False else: - if not 0 <= num <= 65536: + if not 0 <= num <= 65536 or len(part) > 4: return False + max_groups = 6 if ipv4_groups else 8 if count_blank == 0 and len(ipv6_groups) == max_groups: # no :: -> must have size of max_groups return True From 70183861a71b212f2d4bf6825cc00f3a5d40bab5 Mon Sep 17 00:00:00 2001 From: SimonIT Date: Sun, 20 Feb 2022 18:17:32 +0100 Subject: [PATCH 5/7] Check for length --- tests/test_ipv6.py | 2 ++ validators/ip_address.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_ipv6.py b/tests/test_ipv6.py index cac0eb72..286f1fb5 100644 --- a/tests/test_ipv6.py +++ b/tests/test_ipv6.py @@ -24,6 +24,8 @@ def test_returns_true_on_valid_ipv6_address(address): ('abc.0.0.1',), ('abcd:1234::123::1',), ('1:2:3:4:5:6:7:8:9',), + ('1:2:3:4:5:6:7:8::',), + ('1:2:3:4:5:6:7::8:9',), ('abcd::1ffff',), ('1111:',), (':8888',), diff --git a/validators/ip_address.py b/validators/ip_address.py index 469d76e8..03223090 100644 --- a/validators/ip_address.py +++ b/validators/ip_address.py @@ -112,10 +112,10 @@ def ipv6(value): if count_blank == 0 and len(ipv6_groups) == max_groups: # no :: -> must have size of max_groups return True - elif count_blank == 1 and ipv6_groups[-1] and ipv6_groups[0]: + elif count_blank == 1 and ipv6_groups[-1] and ipv6_groups[0] and len(ipv6_groups) < max_groups: # one :: inside the address or prefix or suffix : -> filter least two cases return True - elif count_blank == 2 and ( + elif count_blank == 2 and len(ipv6_groups) < max_groups and ( ((ipv6_groups[0] and not ipv6_groups[-1]) or (not ipv6_groups[0] and ipv6_groups[-1])) or ipv4_groups): # leading or trailing :: or : at end and begin -> filter last case # Check if it has ipv4 groups because they get removed from the ipv6_groups From b26a9b8663ebb7a809d61ac97f63d7728177d361 Mon Sep 17 00:00:00 2001 From: SimonIT Date: Sun, 20 Feb 2022 18:28:33 +0100 Subject: [PATCH 6/7] Take empty parts in account --- validators/ip_address.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/validators/ip_address.py b/validators/ip_address.py index 03223090..c310d7ea 100644 --- a/validators/ip_address.py +++ b/validators/ip_address.py @@ -109,13 +109,14 @@ def ipv6(value): return False max_groups = 6 if ipv4_groups else 8 + part_count = len(ipv6_groups) - count_blank if count_blank == 0 and len(ipv6_groups) == max_groups: # no :: -> must have size of max_groups return True - elif count_blank == 1 and ipv6_groups[-1] and ipv6_groups[0] and len(ipv6_groups) < max_groups: + elif count_blank == 1 and ipv6_groups[-1] and ipv6_groups[0] and part_count < max_groups: # one :: inside the address or prefix or suffix : -> filter least two cases return True - elif count_blank == 2 and len(ipv6_groups) < max_groups and ( + elif count_blank == 2 and part_count < max_groups and ( ((ipv6_groups[0] and not ipv6_groups[-1]) or (not ipv6_groups[0] and ipv6_groups[-1])) or ipv4_groups): # leading or trailing :: or : at end and begin -> filter last case # Check if it has ipv4 groups because they get removed from the ipv6_groups From 0c5b97cea732291c621e901835f561c002b57c25 Mon Sep 17 00:00:00 2001 From: SimonIT Date: Sun, 20 Feb 2022 18:30:15 +0100 Subject: [PATCH 7/7] Reduce length calculation --- validators/ip_address.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/validators/ip_address.py b/validators/ip_address.py index c310d7ea..f8b138d1 100644 --- a/validators/ip_address.py +++ b/validators/ip_address.py @@ -110,7 +110,7 @@ def ipv6(value): max_groups = 6 if ipv4_groups else 8 part_count = len(ipv6_groups) - count_blank - if count_blank == 0 and len(ipv6_groups) == max_groups: + if count_blank == 0 and part_count == max_groups: # no :: -> must have size of max_groups return True elif count_blank == 1 and ipv6_groups[-1] and ipv6_groups[0] and part_count < max_groups: @@ -121,7 +121,7 @@ def ipv6(value): # leading or trailing :: or : at end and begin -> filter last case # Check if it has ipv4 groups because they get removed from the ipv6_groups return True - elif count_blank == 3 and len(ipv6_groups) == 3: + elif count_blank == 3 and part_count == 0: # :: is the address -> filter everything else return True return False