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 |