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

115 statements  

1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license 

2 

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. 

17 

18"""IPv6 helper functions.""" 

19 

20import binascii 

21import re 

22 

23import dns.exception 

24import dns.ipv4 

25 

26_leading_zero = re.compile(r"0+([0-9a-f]+)") 

27 

28 

29def inet_ntoa(address: bytes) -> str: 

30 """Convert an IPv6 address in binary form to text form. 

31 

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 """ 

37 

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 

96 

97 

98_v4_ending = re.compile(rb"(.*):(\d+\.\d+\.\d+\.\d+)$") 

99_colon_colon_start = re.compile(rb"::.*") 

100_colon_colon_end = re.compile(rb".*::$") 

101 

102 

103def inet_aton(text: str | bytes, ignore_scope: bool = False) -> bytes: 

104 """Convert an IPv6 address in text form to binary form. 

105 

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 """ 

113 

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 

121 

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 

129 

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) 

184 

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 

192 

193 

194_mapped_prefix = b"\x00" * 10 + b"\xff\xff" 

195 

196 

197def is_mapped(address: bytes) -> bool: 

198 """Is the specified address a mapped IPv4 address? 

199 

200 :param address: An IPv6 address in binary form. 

201 :type address: bytes 

202 :rtype: bool 

203 """ 

204 

205 return address.startswith(_mapped_prefix) 

206 

207 

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. 

211 

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))