Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dns/ipv6.py: 12%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
3# Copyright (C) 2003-2017 Nominum, Inc.
4#
5# Permission to use, copy, modify, and distribute this software and its
6# documentation for any purpose with or without fee is hereby granted,
7# provided that the above copyright notice and this permission notice
8# appear in all copies.
9#
10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18"""IPv6 helper functions."""
20import binascii
21import re
23import dns.exception
24import dns.ipv4
26_leading_zero = re.compile(r"0+([0-9a-f]+)")
29def inet_ntoa(address: bytes) -> str:
30 """Convert an IPv6 address in binary form to text form.
32 :param address: The IPv6 address in binary form.
33 :type address: bytes
34 :raises ValueError: If the address is not 16 bytes long.
35 :rtype: str
36 """
38 if len(address) != 16:
39 raise ValueError("IPv6 addresses are 16 bytes long")
40 hex = binascii.hexlify(address)
41 chunks = []
42 i = 0
43 l = len(hex)
44 while i < l:
45 chunk = hex[i : i + 4].decode()
46 # strip leading zeros. we do this with an re instead of
47 # with lstrip() because lstrip() didn't support chars until
48 # python 2.2.2
49 m = _leading_zero.match(chunk)
50 if m is not None:
51 chunk = m.group(1)
52 chunks.append(chunk)
53 i += 4
54 #
55 # Compress the longest subsequence of 0-value chunks to ::
56 #
57 best_start = 0
58 best_len = 0
59 start = -1
60 last_was_zero = False
61 for i in range(8):
62 if chunks[i] != "0":
63 if last_was_zero:
64 end = i
65 current_len = end - start
66 if current_len > best_len:
67 best_start = start
68 best_len = current_len
69 last_was_zero = False
70 elif not last_was_zero:
71 start = i
72 last_was_zero = True
73 if last_was_zero:
74 end = 8
75 current_len = end - start
76 if current_len > best_len:
77 best_start = start
78 best_len = current_len
79 if best_len > 1:
80 if best_start == 0 and (best_len == 6 or best_len == 5 and chunks[5] == "ffff"):
81 # We have an embedded IPv4 address
82 if best_len == 6:
83 prefix = "::"
84 else:
85 prefix = "::ffff:"
86 thex = prefix + dns.ipv4.inet_ntoa(address[12:])
87 else:
88 thex = (
89 ":".join(chunks[:best_start])
90 + "::"
91 + ":".join(chunks[best_start + best_len :])
92 )
93 else:
94 thex = ":".join(chunks)
95 return thex
98_v4_ending = re.compile(rb"(.*):(\d+\.\d+\.\d+\.\d+)$")
99_colon_colon_start = re.compile(rb"::.*")
100_colon_colon_end = re.compile(rb".*::$")
103def inet_aton(text: str | bytes, ignore_scope: bool = False) -> bytes:
104 """Convert an IPv6 address in text form to binary form.
106 :param text: The IPv6 address in textual form.
107 :type text: str or bytes
108 :param ignore_scope: If ``True``, a scope is ignored; if ``False``
109 (the default), a scope is an error.
110 :type ignore_scope: bool
111 :rtype: bytes
112 """
114 #
115 # Our aim here is not something fast; we just want something that works.
116 #
117 if not isinstance(text, bytes):
118 btext = text.encode()
119 else:
120 btext = text
122 if ignore_scope:
123 parts = btext.split(b"%")
124 l = len(parts)
125 if l == 2:
126 btext = parts[0]
127 elif l > 2:
128 raise dns.exception.SyntaxError
130 if btext == b"":
131 raise dns.exception.SyntaxError
132 elif btext.endswith(b":") and not btext.endswith(b"::"):
133 raise dns.exception.SyntaxError
134 elif btext.startswith(b":") and not btext.startswith(b"::"):
135 raise dns.exception.SyntaxError
136 elif btext == b"::":
137 btext = b"0::"
138 #
139 # Get rid of the icky dot-quad syntax if we have it.
140 #
141 m = _v4_ending.match(btext)
142 if m is not None:
143 b = dns.ipv4.inet_aton(m.group(2))
144 btext = (
145 f"{m.group(1).decode()}:{b[0]:02x}{b[1]:02x}:{b[2]:02x}{b[3]:02x}"
146 ).encode()
147 #
148 # Try to turn '::<whatever>' into ':<whatever>'; if no match try to
149 # turn '<whatever>::' into '<whatever>:'
150 #
151 m = _colon_colon_start.match(btext)
152 if m is not None:
153 btext = btext[1:]
154 else:
155 m = _colon_colon_end.match(btext)
156 if m is not None:
157 btext = btext[:-1]
158 #
159 # Now canonicalize into 8 chunks of 4 hex digits each
160 #
161 chunks = btext.split(b":")
162 l = len(chunks)
163 if l > 8:
164 raise dns.exception.SyntaxError
165 seen_empty = False
166 canonical: list[bytes] = []
167 for c in chunks:
168 if c == b"":
169 if seen_empty:
170 raise dns.exception.SyntaxError
171 seen_empty = True
172 for _ in range(0, 8 - l + 1):
173 canonical.append(b"0000")
174 else:
175 lc = len(c)
176 if lc > 4:
177 raise dns.exception.SyntaxError
178 if lc != 4:
179 c = (b"0" * (4 - lc)) + c
180 canonical.append(c)
181 if l < 8 and not seen_empty:
182 raise dns.exception.SyntaxError
183 btext = b"".join(canonical)
185 #
186 # Finally we can go to binary.
187 #
188 try:
189 return binascii.unhexlify(btext)
190 except (binascii.Error, TypeError):
191 raise dns.exception.SyntaxError
194_mapped_prefix = b"\x00" * 10 + b"\xff\xff"
197def is_mapped(address: bytes) -> bool:
198 """Is the specified address a mapped IPv4 address?
200 :param address: An IPv6 address in binary form.
201 :type address: bytes
202 :rtype: bool
203 """
205 return address.startswith(_mapped_prefix)
208def canonicalize(text: str | bytes) -> str:
209 """Verify that *address* is a valid text form IPv6 address and return its
210 canonical text form. Addresses with scopes are rejected.
212 :param text: The IPv6 address in textual form.
213 :type text: str or bytes
214 :raises dns.exception.SyntaxError: If the text is not valid.
215 """
216 return inet_ntoa(inet_aton(text))