Project

General

Profile

Statistics
| Branch: | Revision:

root / env / lib / python2.7 / site-packages / django / utils / ipv6.py @ 1a305335

History | View | Annotate | Download (7.69 KB)

1 1a305335 officers
# This code was mostly based on ipaddr-py
2
# Copyright 2007 Google Inc. http://code.google.com/p/ipaddr-py/
3
# Licensed under the Apache License, Version 2.0 (the "License").
4
from django.core.exceptions import ValidationError
5
6
def clean_ipv6_address(ip_str, unpack_ipv4=False,
7
        error_message="This is not a valid IPv6 address"):
8
    """
9
    Cleans a IPv6 address string.
10

11
    Validity is checked by calling is_valid_ipv6_address() - if an
12
    invalid address is passed, ValidationError is raised.
13

14
    Replaces the longest continious zero-sequence with "::" and
15
    removes leading zeroes and makes sure all hextets are lowercase.
16

17
    Args:
18
        ip_str: A valid IPv6 address.
19
        unpack_ipv4: if an IPv4-mapped address is found,
20
        return the plain IPv4 address (default=False).
21
        error_message: A error message for in the ValidationError.
22

23
    Returns:
24
        A compressed IPv6 address, or the same value
25

26
    """
27
    best_doublecolon_start = -1
28
    best_doublecolon_len = 0
29
    doublecolon_start = -1
30
    doublecolon_len = 0
31
32
    if not is_valid_ipv6_address(ip_str):
33
        raise ValidationError(error_message)
34
35
    # This algorithm can only handle fully exploded
36
    # IP strings
37
    ip_str = _explode_shorthand_ip_string(ip_str)
38
39
    ip_str = _sanitize_ipv4_mapping(ip_str)
40
41
    # If needed, unpack the IPv4 and return straight away
42
    # - no need in running the rest of the algorithm
43
    if unpack_ipv4:
44
        ipv4_unpacked = _unpack_ipv4(ip_str)
45
46
        if ipv4_unpacked:
47
            return ipv4_unpacked
48
49
    hextets = ip_str.split(":")
50
51
    for index in range(len(hextets)):
52
        # Remove leading zeroes
53
        hextets[index] = hextets[index].lstrip('0')
54
        if not hextets[index]:
55
            hextets[index] = '0'
56
57
        # Determine best hextet to compress
58
        if hextets[index] == '0':
59
            doublecolon_len += 1
60
            if doublecolon_start == -1:
61
                # Start of a sequence of zeros.
62
                doublecolon_start = index
63
            if doublecolon_len > best_doublecolon_len:
64
                # This is the longest sequence of zeros so far.
65
                best_doublecolon_len = doublecolon_len
66
                best_doublecolon_start = doublecolon_start
67
        else:
68
            doublecolon_len = 0
69
            doublecolon_start = -1
70
71
    # Compress the most suitable hextet
72
    if best_doublecolon_len > 1:
73
        best_doublecolon_end = (best_doublecolon_start +
74
                                best_doublecolon_len)
75
        # For zeros at the end of the address.
76
        if best_doublecolon_end == len(hextets):
77
            hextets += ['']
78
        hextets[best_doublecolon_start:best_doublecolon_end] = ['']
79
        # For zeros at the beginning of the address.
80
        if best_doublecolon_start == 0:
81
            hextets = [''] + hextets
82
83
    result = ":".join(hextets)
84
85
    return result.lower()
86
87
88
def _sanitize_ipv4_mapping(ip_str):
89
    """
90
    Sanitize IPv4 mapping in a expanded IPv6 address.
91

92
    This converts ::ffff:0a0a:0a0a to ::ffff:10.10.10.10.
93
    If there is nothing to sanitize, returns an unchanged
94
    string.
95

96
    Args:
97
        ip_str: A string, the expanded IPv6 address.
98

99
    Returns:
100
        The sanitized output string, if applicable.
101
    """
102
    if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
103
        # not an ipv4 mapping
104
        return ip_str
105
106
    hextets = ip_str.split(':')
107
108
    if '.' in hextets[-1]:
109
        # already sanitized
110
        return ip_str
111
112
    ipv4_address = "%d.%d.%d.%d" % (
113
        int(hextets[6][0:2], 16),
114
        int(hextets[6][2:4], 16),
115
        int(hextets[7][0:2], 16),
116
        int(hextets[7][2:4], 16),
117
    )
118
119
    result = ':'.join(hextets[0:6])
120
    result += ':' + ipv4_address
121
122
    return result
123
124
def _unpack_ipv4(ip_str):
125
    """
126
    Unpack an IPv4 address that was mapped in a compressed IPv6 address.
127

128
    This converts 0000:0000:0000:0000:0000:ffff:10.10.10.10 to 10.10.10.10.
129
    If there is nothing to sanitize, returns None.
130

131
    Args:
132
        ip_str: A string, the expanded IPv6 address.
133

134
    Returns:
135
        The unpacked IPv4 address, or None if there was nothing to unpack.
136
    """
137
    if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
138
        return None
139
140
    hextets = ip_str.split(':')
141
    return hextets[-1]
142
143
def is_valid_ipv6_address(ip_str):
144
    """
145
    Ensure we have a valid IPv6 address.
146

147
    Args:
148
        ip_str: A string, the IPv6 address.
149

150
    Returns:
151
        A boolean, True if this is a valid IPv6 address.
152

153
    """
154
    from django.core.validators import validate_ipv4_address
155
156
    # We need to have at least one ':'.
157
    if ':' not in ip_str:
158
        return False
159
160
    # We can only have one '::' shortener.
161
    if ip_str.count('::') > 1:
162
        return False
163
164
    # '::' should be encompassed by start, digits or end.
165
    if ':::' in ip_str:
166
        return False
167
168
    # A single colon can neither start nor end an address.
169
    if ((ip_str.startswith(':') and not ip_str.startswith('::')) or
170
            (ip_str.endswith(':') and not ip_str.endswith('::'))):
171
        return False
172
173
    # We can never have more than 7 ':' (1::2:3:4:5:6:7:8 is invalid)
174
    if ip_str.count(':') > 7:
175
        return False
176
177
    # If we have no concatenation, we need to have 8 fields with 7 ':'.
178
    if '::' not in ip_str and ip_str.count(':') != 7:
179
        # We might have an IPv4 mapped address.
180
        if ip_str.count('.') != 3:
181
            return False
182
183
    ip_str = _explode_shorthand_ip_string(ip_str)
184
185
    # Now that we have that all squared away, let's check that each of the
186
    # hextets are between 0x0 and 0xFFFF.
187
    for hextet in ip_str.split(':'):
188
        if hextet.count('.') == 3:
189
            # If we have an IPv4 mapped address, the IPv4 portion has to
190
            # be at the end of the IPv6 portion.
191
            if not ip_str.split(':')[-1] == hextet:
192
                return False
193
            try:
194
                validate_ipv4_address(hextet)
195
            except ValidationError:
196
                return False
197
        else:
198
            try:
199
                # a value error here means that we got a bad hextet,
200
                # something like 0xzzzz
201
                if int(hextet, 16) < 0x0 or int(hextet, 16) > 0xFFFF:
202
                    return False
203
            except ValueError:
204
                return False
205
    return True
206
207
208
def _explode_shorthand_ip_string(ip_str):
209
    """
210
    Expand a shortened IPv6 address.
211

212
    Args:
213
        ip_str: A string, the IPv6 address.
214

215
    Returns:
216
        A string, the expanded IPv6 address.
217

218
    """
219
    if not _is_shorthand_ip(ip_str):
220
        # We've already got a longhand ip_str.
221
        return ip_str
222
223
    new_ip = []
224
    hextet = ip_str.split('::')
225
226
    # If there is a ::, we need to expand it with zeroes
227
    # to get to 8 hextets - unless there is a dot in the last hextet,
228
    # meaning we're doing v4-mapping
229
    if '.' in ip_str.split(':')[-1]:
230
        fill_to = 7
231
    else:
232
        fill_to = 8
233
234
    if len(hextet) > 1:
235
        sep = len(hextet[0].split(':')) + len(hextet[1].split(':'))
236
        new_ip = hextet[0].split(':')
237
238
        for _ in xrange(fill_to - sep):
239
            new_ip.append('0000')
240
        new_ip += hextet[1].split(':')
241
242
    else:
243
        new_ip = ip_str.split(':')
244
245
    # Now need to make sure every hextet is 4 lower case characters.
246
    # If a hextet is < 4 characters, we've got missing leading 0's.
247
    ret_ip = []
248
    for hextet in new_ip:
249
        ret_ip.append(('0' * (4 - len(hextet)) + hextet).lower())
250
    return ':'.join(ret_ip)
251
252
253
def _is_shorthand_ip(ip_str):
254
    """Determine if the address is shortened.
255

256
    Args:
257
        ip_str: A string, the IPv6 address.
258

259
    Returns:
260
        A boolean, True if the address is shortened.
261

262
    """
263
    if ip_str.count('::') == 1:
264
        return True
265
    if filter(lambda x: len(x) < 4, ip_str.split(':')):
266
        return True
267
    return False