summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2017-01-29 20:07:50 +0100
committerClaude Paroz <claude@2xlibre.net>2017-01-30 14:22:18 +0100
commit277a4dd4b4cc2a2cad77139882f084480751a95a (patch)
treee7e7a873d435c0cabfeaaf6d7ed6fa8774ebb222
parentc688336ebcc1bddc65f2d48e15b981b6caa7ef1a (diff)
Fixed #27793 -- Used stdlib's ipaddress module to validate IP addresses
Thanks Tim Graham for the review.
-rw-r--r--django/core/validators.py9
-rw-r--r--django/utils/ipv6.py254
2 files changed, 24 insertions, 239 deletions
diff --git a/django/core/validators.py b/django/core/validators.py
index 732bcae486..365c6a7f15 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -1,3 +1,4 @@
+import ipaddress
import os
import re
from urllib.parse import urlsplit, urlunsplit
@@ -247,8 +248,12 @@ validate_unicode_slug = RegexValidator(
'invalid'
)
-ipv4_re = _lazy_re_compile(r'^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])){3}\Z')
-validate_ipv4_address = RegexValidator(ipv4_re, _('Enter a valid IPv4 address.'), 'invalid')
+
+def validate_ipv4_address(value):
+ try:
+ ipaddress.IPv4Address(value)
+ except ValueError:
+ raise ValidationError(_('Enter a valid IPv4 address.'), code='invalid')
def validate_ipv6_address(value):
diff --git a/django/utils/ipv6.py b/django/utils/ipv6.py
index 93a1bfd1b2..4eb59e6021 100644
--- a/django/utils/ipv6.py
+++ b/django/utils/ipv6.py
@@ -1,7 +1,4 @@
-# This code was mostly based on ipaddr-py
-# Copyright 2007 Google Inc. https://github.com/google/ipaddr-py
-# Licensed under the Apache License, Version 2.0 (the "License").
-import re
+import ipaddress
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
@@ -10,13 +7,12 @@ from django.utils.translation import ugettext_lazy as _
def clean_ipv6_address(ip_str, unpack_ipv4=False,
error_message=_("This is not a valid IPv6 address.")):
"""
- Cleans an IPv6 address string.
+ Clean an IPv6 address string.
- Validity is checked by calling is_valid_ipv6_address() - if an
- invalid address is passed, ValidationError is raised.
+ Raise ValidationError if the address is invalid.
- Replaces the longest continuous zero-sequence with "::" and
- removes leading zeroes and makes sure all hextets are lowercase.
+ Replace the longest continuous zero-sequence with "::", remove leading
+ zeroes, and make sure all hextets are lowercase.
Args:
ip_str: A valid IPv6 address.
@@ -24,125 +20,19 @@ def clean_ipv6_address(ip_str, unpack_ipv4=False,
return the plain IPv4 address (default=False).
error_message: An error message used in the ValidationError.
- Returns:
- A compressed IPv6 address, or the same value
+ Return a compressed IPv6 address or the same value.
"""
- best_doublecolon_start = -1
- best_doublecolon_len = 0
- doublecolon_start = -1
- doublecolon_len = 0
-
- if not is_valid_ipv6_address(ip_str):
+ try:
+ addr = ipaddress.IPv6Address(int(ipaddress.IPv6Address(ip_str)))
+ except ValueError:
raise ValidationError(error_message, code='invalid')
- # This algorithm can only handle fully exploded
- # IP strings
- ip_str = _explode_shorthand_ip_string(ip_str)
-
- ip_str = _sanitize_ipv4_mapping(ip_str)
-
- # If needed, unpack the IPv4 and return straight away
- # - no need in running the rest of the algorithm
- if unpack_ipv4:
- ipv4_unpacked = _unpack_ipv4(ip_str)
-
- if ipv4_unpacked:
- return ipv4_unpacked
-
- hextets = ip_str.split(":")
-
- for index in range(len(hextets)):
- # Remove leading zeroes
- if '.' not in hextets[index]:
- hextets[index] = hextets[index].lstrip('0')
- if not hextets[index]:
- hextets[index] = '0'
-
- # Determine best hextet to compress
- if hextets[index] == '0':
- doublecolon_len += 1
- if doublecolon_start == -1:
- # Start of a sequence of zeros.
- doublecolon_start = index
- if doublecolon_len > best_doublecolon_len:
- # This is the longest sequence of zeros so far.
- best_doublecolon_len = doublecolon_len
- best_doublecolon_start = doublecolon_start
- else:
- doublecolon_len = 0
- doublecolon_start = -1
-
- # Compress the most suitable hextet
- if best_doublecolon_len > 1:
- best_doublecolon_end = (best_doublecolon_start +
- best_doublecolon_len)
- # For zeros at the end of the address.
- if best_doublecolon_end == len(hextets):
- hextets += ['']
- hextets[best_doublecolon_start:best_doublecolon_end] = ['']
- # For zeros at the beginning of the address.
- if best_doublecolon_start == 0:
- hextets = [''] + hextets
-
- result = ":".join(hextets)
-
- return result.lower()
-
-
-def _sanitize_ipv4_mapping(ip_str):
- """
- Sanitize IPv4 mapping in an expanded IPv6 address.
-
- This converts ::ffff:0a0a:0a0a to ::ffff:10.10.10.10.
- If there is nothing to sanitize, returns an unchanged
- string.
-
- Args:
- ip_str: A string, the expanded IPv6 address.
-
- Returns:
- The sanitized output string, if applicable.
- """
- if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
- # not an ipv4 mapping
- return ip_str
-
- hextets = ip_str.split(':')
-
- if '.' in hextets[-1]:
- # already sanitized
- return ip_str
-
- ipv4_address = "%d.%d.%d.%d" % (
- int(hextets[6][0:2], 16),
- int(hextets[6][2:4], 16),
- int(hextets[7][0:2], 16),
- int(hextets[7][2:4], 16),
- )
-
- result = ':'.join(hextets[0:6])
- result += ':' + ipv4_address
-
- return result
-
-
-def _unpack_ipv4(ip_str):
- """
- Unpack an IPv4 address that was mapped in a compressed IPv6 address.
-
- This converts 0000:0000:0000:0000:0000:ffff:10.10.10.10 to 10.10.10.10.
- If there is nothing to sanitize, returns None.
+ if unpack_ipv4 and addr.ipv4_mapped:
+ return str(addr.ipv4_mapped)
+ elif addr.ipv4_mapped:
+ return '::ffff:%s' % str(addr.ipv4_mapped)
- Args:
- ip_str: A string, the expanded IPv6 address.
-
- Returns:
- The unpacked IPv4 address, or None if there was nothing to unpack.
- """
- if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
- return None
-
- return ip_str.rsplit(':', 1)[1]
+ return str(addr)
def is_valid_ipv6_address(ip_str):
@@ -155,118 +45,8 @@ def is_valid_ipv6_address(ip_str):
Returns:
A boolean, True if this is a valid IPv6 address.
"""
- from django.core.validators import validate_ipv4_address
-
- symbols_re = re.compile(r'^[0-9a-fA-F:.]+$')
- if not symbols_re.match(ip_str):
+ try:
+ ipaddress.IPv6Address(ip_str)
+ except ValueError:
return False
-
- # We need to have at least one ':'.
- if ':' not in ip_str:
- return False
-
- # We can only have one '::' shortener.
- if ip_str.count('::') > 1:
- return False
-
- # '::' should be encompassed by start, digits or end.
- if ':::' in ip_str:
- return False
-
- # A single colon can neither start nor end an address.
- if ((ip_str.startswith(':') and not ip_str.startswith('::')) or
- (ip_str.endswith(':') and not ip_str.endswith('::'))):
- return False
-
- # We can never have more than 7 ':' (1::2:3:4:5:6:7:8 is invalid)
- if ip_str.count(':') > 7:
- return False
-
- # If we have no concatenation, we need to have 8 fields with 7 ':'.
- if '::' not in ip_str and ip_str.count(':') != 7:
- # We might have an IPv4 mapped address.
- if ip_str.count('.') != 3:
- return False
-
- ip_str = _explode_shorthand_ip_string(ip_str)
-
- # Now that we have that all squared away, let's check that each of the
- # hextets are between 0x0 and 0xFFFF.
- for hextet in ip_str.split(':'):
- if hextet.count('.') == 3:
- # If we have an IPv4 mapped address, the IPv4 portion has to
- # be at the end of the IPv6 portion.
- if not ip_str.split(':')[-1] == hextet:
- return False
- try:
- validate_ipv4_address(hextet)
- except ValidationError:
- return False
- else:
- try:
- # a value error here means that we got a bad hextet,
- # something like 0xzzzz
- if int(hextet, 16) < 0x0 or int(hextet, 16) > 0xFFFF:
- return False
- except ValueError:
- return False
return True
-
-
-def _explode_shorthand_ip_string(ip_str):
- """
- Expand a shortened IPv6 address.
-
- Args:
- ip_str: A string, the IPv6 address.
-
- Returns:
- A string, the expanded IPv6 address.
- """
- if not _is_shorthand_ip(ip_str):
- # We've already got a longhand ip_str.
- return ip_str
-
- hextet = ip_str.split('::')
-
- # If there is a ::, we need to expand it with zeroes
- # to get to 8 hextets - unless there is a dot in the last hextet,
- # meaning we're doing v4-mapping
- if '.' in ip_str.split(':')[-1]:
- fill_to = 7
- else:
- fill_to = 8
-
- if len(hextet) > 1:
- sep = len(hextet[0].split(':')) + len(hextet[1].split(':'))
- new_ip = hextet[0].split(':')
-
- for __ in range(fill_to - sep):
- new_ip.append('0000')
- new_ip += hextet[1].split(':')
-
- else:
- new_ip = ip_str.split(':')
-
- # Now need to make sure every hextet is 4 lower case characters.
- # If a hextet is < 4 characters, we've got missing leading 0's.
- ret_ip = []
- for hextet in new_ip:
- ret_ip.append(('0' * (4 - len(hextet)) + hextet).lower())
- return ':'.join(ret_ip)
-
-
-def _is_shorthand_ip(ip_str):
- """Determine if the address is shortened.
-
- Args:
- ip_str: A string, the IPv6 address.
-
- Returns:
- A boolean, True if the address is shortened.
- """
- if ip_str.count('::') == 1:
- return True
- if any(len(x) < 4 for x in ip_str.split(':')):
- return True
- return False