Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/netaddr/ip/glob.py: 14%

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

137 statements  

1# ----------------------------------------------------------------------------- 

2# Copyright (c) 2008 by David P. D. Moss. All rights reserved. 

3# 

4# Released under the BSD license. See the LICENSE file for details. 

5# ----------------------------------------------------------------------------- 

6""" 

7Routines and classes for supporting and expressing IP address ranges using a 

8glob style syntax. 

9 

10""" 

11from netaddr.core import AddrFormatError, AddrConversionError 

12from netaddr.ip import IPRange, IPAddress, IPNetwork, iprange_to_cidrs 

13 

14 

15def valid_glob(ipglob): 

16 """ 

17 :param ipglob: An IP address range in a glob-style format. 

18 

19 :return: ``True`` if IP range glob is valid, ``False`` otherwise. 

20 """ 

21 # TODO: Add support for abbreviated ipglobs. 

22 # TODO: e.g. 192.0.*.* == 192.0.* 

23 # TODO: *.*.*.* == * 

24 # TODO: Add strict flag to enable verbose ipglob checking. 

25 if not isinstance(ipglob, str): 

26 return False 

27 

28 seen_hyphen = False 

29 seen_asterisk = False 

30 

31 octets = ipglob.split('.') 

32 

33 if len(octets) != 4: 

34 return False 

35 

36 for octet in octets: 

37 if '-' in octet: 

38 if seen_hyphen: 

39 return False 

40 seen_hyphen = True 

41 if seen_asterisk: 

42 # Asterisks cannot precede hyphenated octets. 

43 return False 

44 try: 

45 (octet1, octet2) = [int(i) for i in octet.split('-')] 

46 except ValueError: 

47 return False 

48 if octet1 >= octet2: 

49 return False 

50 if not 0 <= octet1 <= 254: 

51 return False 

52 if not 1 <= octet2 <= 255: 

53 return False 

54 elif octet == '*': 

55 seen_asterisk = True 

56 else: 

57 if seen_hyphen is True: 

58 return False 

59 if seen_asterisk is True: 

60 return False 

61 try: 

62 if not 0 <= int(octet) <= 255: 

63 return False 

64 except ValueError: 

65 return False 

66 return True 

67 

68 

69def glob_to_iptuple(ipglob): 

70 """ 

71 A function that accepts a glob-style IP range and returns the component 

72 lower and upper bound IP address. 

73 

74 :param ipglob: an IP address range in a glob-style format. 

75 

76 :return: a tuple contain lower and upper bound IP objects. 

77 """ 

78 if not valid_glob(ipglob): 

79 raise AddrFormatError('not a recognised IP glob range: %r!' % (ipglob,)) 

80 

81 start_tokens = [] 

82 end_tokens = [] 

83 

84 for octet in ipglob.split('.'): 

85 if '-' in octet: 

86 tokens = octet.split('-') 

87 start_tokens.append(tokens[0]) 

88 end_tokens.append(tokens[1]) 

89 elif octet == '*': 

90 start_tokens.append('0') 

91 end_tokens.append('255') 

92 else: 

93 start_tokens.append(octet) 

94 end_tokens.append(octet) 

95 

96 return IPAddress('.'.join(start_tokens)), IPAddress('.'.join(end_tokens)) 

97 

98 

99def glob_to_iprange(ipglob): 

100 """ 

101 A function that accepts a glob-style IP range and returns the equivalent 

102 IP range. 

103 

104 :param ipglob: an IP address range in a glob-style format. 

105 

106 :return: an IPRange object. 

107 """ 

108 if not valid_glob(ipglob): 

109 raise AddrFormatError('not a recognised IP glob range: %r!' % (ipglob,)) 

110 

111 start_tokens = [] 

112 end_tokens = [] 

113 

114 for octet in ipglob.split('.'): 

115 if '-' in octet: 

116 tokens = octet.split('-') 

117 start_tokens.append(tokens[0]) 

118 end_tokens.append(tokens[1]) 

119 elif octet == '*': 

120 start_tokens.append('0') 

121 end_tokens.append('255') 

122 else: 

123 start_tokens.append(octet) 

124 end_tokens.append(octet) 

125 

126 return IPRange('.'.join(start_tokens), '.'.join(end_tokens)) 

127 

128 

129def iprange_to_globs(start, end): 

130 """ 

131 A function that accepts an arbitrary start and end IP address or subnet 

132 and returns one or more glob-style IP ranges. 

133 

134 :param start: the start IP address or subnet. 

135 

136 :param end: the end IP address or subnet. 

137 

138 :return: a list containing one or more IP globs. 

139 """ 

140 start = IPAddress(start) 

141 end = IPAddress(end) 

142 

143 if start.version != 4 and end.version != 4: 

144 raise AddrConversionError('IP glob ranges only support IPv4!') 

145 

146 def _iprange_to_glob(lb, ub): 

147 # Internal function to process individual IP globs. 

148 t1 = [int(_) for _ in str(lb).split('.')] 

149 t2 = [int(_) for _ in str(ub).split('.')] 

150 

151 tokens = [] 

152 

153 seen_hyphen = False 

154 seen_asterisk = False 

155 

156 for i in range(4): 

157 if t1[i] == t2[i]: 

158 # A normal octet. 

159 tokens.append(str(t1[i])) 

160 elif (t1[i] == 0) and (t2[i] == 255): 

161 # An asterisk octet. 

162 tokens.append('*') 

163 seen_asterisk = True 

164 else: 

165 # Create a hyphenated octet - only one allowed per IP glob. 

166 if not seen_asterisk: 

167 if not seen_hyphen: 

168 tokens.append('%s-%s' % (t1[i], t2[i])) 

169 seen_hyphen = True 

170 else: 

171 raise AddrConversionError('only 1 hyphenated octet per IP glob allowed!') 

172 else: 

173 raise AddrConversionError('asterisks are not allowed before hyphenated octets!') 

174 

175 return '.'.join(tokens) 

176 

177 globs = [] 

178 

179 try: 

180 # IP range can be represented by a single glob. 

181 ipglob = _iprange_to_glob(start, end) 

182 if not valid_glob(ipglob): 

183 # TODO: this is a workaround, it is produces non-optimal but valid 

184 # TODO: glob conversions. Fix inner function so that is always 

185 # TODO: produces a valid glob. 

186 raise AddrConversionError('invalid ip glob created') 

187 globs.append(ipglob) 

188 except AddrConversionError: 

189 # Break IP range up into CIDRs before conversion to globs. 

190 # 

191 # TODO: this is still not completely optimised but is good enough 

192 # TODO: for the moment. 

193 # 

194 for cidr in iprange_to_cidrs(start, end): 

195 ipglob = _iprange_to_glob(cidr[0], cidr[-1]) 

196 globs.append(ipglob) 

197 

198 return globs 

199 

200 

201def glob_to_cidrs(ipglob): 

202 """ 

203 A function that accepts a glob-style IP range and returns a list of one 

204 or more IP CIDRs that exactly matches it. 

205 

206 :param ipglob: an IP address range in a glob-style format. 

207 

208 :return: a list of one or more IP objects. 

209 """ 

210 return iprange_to_cidrs(*glob_to_iptuple(ipglob)) 

211 

212 

213def cidr_to_glob(cidr): 

214 """ 

215 A function that accepts an IP subnet in a glob-style format and returns 

216 a list of CIDR subnets that exactly matches the specified glob. 

217 

218 :param cidr: an IP object CIDR subnet. 

219 

220 :return: a list of one or more IP addresses and subnets. 

221 """ 

222 ip = IPNetwork(cidr) 

223 globs = iprange_to_globs(ip[0], ip[-1]) 

224 if len(globs) != 1: 

225 # There should only ever be a one to one mapping between a CIDR and 

226 # an IP glob range. 

227 raise AddrConversionError('bad CIDR to IP glob conversion!') 

228 return globs[0] 

229 

230 

231class IPGlob(IPRange): 

232 """ 

233 Represents an IP address range using a glob-style syntax ``x.x.x-y.*`` 

234 

235 Individual octets can be represented using the following shortcuts : 

236 

237 1. ``*`` - the asterisk octet (represents values ``0`` through ``255``) 

238 2. ``x-y`` - the hyphenated octet (represents values ``x`` through ``y``) 

239 

240 A few basic rules also apply : 

241 

242 1. ``x`` must always be less than ``y``, therefore : 

243 

244 - ``x`` can only be ``0`` through ``254`` 

245 - ``y`` can only be ``1`` through ``255`` 

246 

247 2. only one hyphenated octet per IP glob is allowed 

248 3. only asterisks are permitted after a hyphenated octet 

249 

250 Examples: 

251 

252 +------------------+------------------------------+ 

253 | IP glob | Description | 

254 +==================+==============================+ 

255 | ``192.0.2.1`` | a single address | 

256 +------------------+------------------------------+ 

257 | ``192.0.2.0-31`` | 32 addresses | 

258 +------------------+------------------------------+ 

259 | ``192.0.2.*`` | 256 addresses | 

260 +------------------+------------------------------+ 

261 | ``192.0.2-3.*`` | 512 addresses | 

262 +------------------+------------------------------+ 

263 | ``192.0-1.*.*`` | 131,072 addresses | 

264 +------------------+------------------------------+ 

265 | ``*.*.*.*`` | the whole IPv4 address space | 

266 +------------------+------------------------------+ 

267 

268 .. note :: \ 

269 IP glob ranges are not directly equivalent to CIDR blocks. \ 

270 They can represent address ranges that do not fall on strict bit mask \ 

271 boundaries. They are suitable for use in configuration files, being \ 

272 more obvious and readable than their CIDR counterparts, especially for \ 

273 admins and end users with little or no networking knowledge or \ 

274 experience. All CIDR addresses can always be represented as IP globs \ 

275 but the reverse is not always true. 

276 """ 

277 

278 __slots__ = ('_glob',) 

279 

280 def __init__(self, ipglob): 

281 (start, end) = glob_to_iptuple(ipglob) 

282 super(IPGlob, self).__init__(start, end) 

283 self.glob = iprange_to_globs(self._start, self._end)[0] 

284 

285 def __getstate__(self): 

286 """:return: Pickled state of an `IPGlob` object.""" 

287 return super(IPGlob, self).__getstate__() 

288 

289 def __setstate__(self, state): 

290 """:param state: data used to unpickle a pickled `IPGlob` object.""" 

291 super(IPGlob, self).__setstate__(state) 

292 self.glob = iprange_to_globs(self._start, self._end)[0] 

293 

294 def _get_glob(self): 

295 return self._glob 

296 

297 def _set_glob(self, ipglob): 

298 (self._start, self._end) = glob_to_iptuple(ipglob) 

299 self._glob = iprange_to_globs(self._start, self._end)[0] 

300 

301 glob = property(_get_glob, _set_glob, None, 'an arbitrary IP address range in glob format.') 

302 

303 def __str__(self): 

304 """:return: IP glob in common representational format.""" 

305 return '%s' % self.glob 

306 

307 def __repr__(self): 

308 """:return: Python statement to create an equivalent object""" 

309 return "%s('%s')" % (self.__class__.__name__, self.glob)