summaryrefslogtreecommitdiff
path: root/lib/python2.7/site-packages/django/utils/ipv6.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/django/utils/ipv6.py')
-rw-r--r--lib/python2.7/site-packages/django/utils/ipv6.py268
1 files changed, 268 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/django/utils/ipv6.py b/lib/python2.7/site-packages/django/utils/ipv6.py
new file mode 100644
index 0000000..4d53522
--- /dev/null
+++ b/lib/python2.7/site-packages/django/utils/ipv6.py
@@ -0,0 +1,268 @@
+# This code was mostly based on ipaddr-py
+# Copyright 2007 Google Inc. http://code.google.com/p/ipaddr-py/
+# Licensed under the Apache License, Version 2.0 (the "License").
+from django.core.exceptions import ValidationError
+from django.utils.translation import ugettext_lazy as _
+from django.utils.six.moves import xrange
+
+def clean_ipv6_address(ip_str, unpack_ipv4=False,
+ error_message=_("This is not a valid IPv6 address.")):
+ """
+ Cleans a IPv6 address string.
+
+ Validity is checked by calling is_valid_ipv6_address() - if an
+ invalid address is passed, ValidationError is raised.
+
+ Replaces the longest continious zero-sequence with "::" and
+ removes leading zeroes and makes sure all hextets are lowercase.
+
+ Args:
+ ip_str: A valid IPv6 address.
+ unpack_ipv4: if an IPv4-mapped address is found,
+ return the plain IPv4 address (default=False).
+ error_message: A error message for in the ValidationError.
+
+ Returns:
+ 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):
+ 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
+ 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 a 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.
+
+ 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]
+
+def is_valid_ipv6_address(ip_str):
+ """
+ Ensure we have a valid IPv6 address.
+
+ Args:
+ ip_str: A string, the IPv6 address.
+
+ Returns:
+ A boolean, True if this is a valid IPv6 address.
+
+ """
+ from django.core.validators import validate_ipv4_address
+
+ # 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
+
+ new_ip = []
+ 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 xrange(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