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

137 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:45 +0000

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 

13from netaddr.compat import _is_str 

14 

15 

16def valid_glob(ipglob): 

17 """ 

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

19 

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

21 """ 

22 #TODO: Add support for abbreviated ipglobs. 

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

24 #TODO: *.*.*.* == * 

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

26 if not _is_str(ipglob): 

27 return False 

28 

29 seen_hyphen = False 

30 seen_asterisk = False 

31 

32 octets = ipglob.split('.') 

33 

34 if len(octets) != 4: 

35 return False 

36 

37 for octet in octets: 

38 if '-' in octet: 

39 if seen_hyphen: 

40 return False 

41 seen_hyphen = True 

42 if seen_asterisk: 

43 # Asterisks cannot precede hyphenated octets. 

44 return False 

45 try: 

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

47 except ValueError: 

48 return False 

49 if octet1 >= octet2: 

50 return False 

51 if not 0 <= octet1 <= 254: 

52 return False 

53 if not 1 <= octet2 <= 255: 

54 return False 

55 elif octet == '*': 

56 seen_asterisk = True 

57 else: 

58 if seen_hyphen is True: 

59 return False 

60 if seen_asterisk is True: 

61 return False 

62 try: 

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

64 return False 

65 except ValueError: 

66 return False 

67 return True 

68 

69 

70def glob_to_iptuple(ipglob): 

71 """ 

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

73 lower and upper bound IP address. 

74 

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

76 

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

78 """ 

79 if not valid_glob(ipglob): 

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

81 

82 start_tokens = [] 

83 end_tokens = [] 

84 

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

86 if '-' in octet: 

87 tokens = octet.split('-') 

88 start_tokens.append(tokens[0]) 

89 end_tokens.append(tokens[1]) 

90 elif octet == '*': 

91 start_tokens.append('0') 

92 end_tokens.append('255') 

93 else: 

94 start_tokens.append(octet) 

95 end_tokens.append(octet) 

96 

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

98 

99 

100def glob_to_iprange(ipglob): 

101 """ 

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

103 IP range. 

104 

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

106 

107 :return: an IPRange object. 

108 """ 

109 if not valid_glob(ipglob): 

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

111 

112 start_tokens = [] 

113 end_tokens = [] 

114 

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

116 if '-' in octet: 

117 tokens = octet.split('-') 

118 start_tokens.append(tokens[0]) 

119 end_tokens.append(tokens[1]) 

120 elif octet == '*': 

121 start_tokens.append('0') 

122 end_tokens.append('255') 

123 else: 

124 start_tokens.append(octet) 

125 end_tokens.append(octet) 

126 

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

128 

129 

130def iprange_to_globs(start, end): 

131 """ 

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

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

134 

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

136 

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

138 

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

140 """ 

141 start = IPAddress(start) 

142 end = IPAddress(end) 

143 

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

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

146 

147 def _iprange_to_glob(lb, ub): 

148 # Internal function to process individual IP globs. 

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

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

151 

152 tokens = [] 

153 

154 seen_hyphen = False 

155 seen_asterisk = False 

156 

157 for i in range(4): 

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

159 # A normal octet. 

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

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

162 # An asterisk octet. 

163 tokens.append('*') 

164 seen_asterisk = True 

165 else: 

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

167 if not seen_asterisk: 

168 if not seen_hyphen: 

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

170 seen_hyphen = True 

171 else: 

172 raise AddrConversionError( 

173 'only 1 hyphenated octet per IP glob allowed!') 

174 else: 

175 raise AddrConversionError( 

176 "asterisks are not allowed before hyphenated octets!") 

177 

178 return '.'.join(tokens) 

179 

180 globs = [] 

181 

182 try: 

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

184 ipglob = _iprange_to_glob(start, end) 

185 if not valid_glob(ipglob): 

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

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

188 #TODO: produces a valid glob. 

189 raise AddrConversionError('invalid ip glob created') 

190 globs.append(ipglob) 

191 except AddrConversionError: 

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

193 # 

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

195 #TODO: for the moment. 

196 # 

197 for cidr in iprange_to_cidrs(start, end): 

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

199 globs.append(ipglob) 

200 

201 return globs 

202 

203 

204def glob_to_cidrs(ipglob): 

205 """ 

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

207 or more IP CIDRs that exactly matches it. 

208 

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

210 

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

212 """ 

213 return iprange_to_cidrs(*glob_to_iptuple(ipglob)) 

214 

215 

216def cidr_to_glob(cidr): 

217 """ 

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

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

220 

221 :param cidr: an IP object CIDR subnet. 

222 

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

224 """ 

225 ip = IPNetwork(cidr) 

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

227 if len(globs) != 1: 

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

229 # an IP glob range. 

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

231 return globs[0] 

232 

233 

234class IPGlob(IPRange): 

235 """ 

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

237 

238 Individual octets can be represented using the following shortcuts : 

239 

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

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

242 

243 A few basic rules also apply : 

244 

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

246 

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

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

249 

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

251 3. only asterisks are permitted after a hyphenated octet 

252 

253 Examples: 

254 

255 +------------------+------------------------------+ 

256 | IP glob | Description | 

257 +==================+==============================+ 

258 | ``192.0.2.1`` | a single address | 

259 +------------------+------------------------------+ 

260 | ``192.0.2.0-31`` | 32 addresses | 

261 +------------------+------------------------------+ 

262 | ``192.0.2.*`` | 256 addresses | 

263 +------------------+------------------------------+ 

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

265 +------------------+------------------------------+ 

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

267 +------------------+------------------------------+ 

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

269 +------------------+------------------------------+ 

270 

271 .. note :: \ 

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

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

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

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

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

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

278 but the reverse is not always true. 

279 """ 

280 __slots__ = ('_glob',) 

281 

282 def __init__(self, ipglob): 

283 (start, end) = glob_to_iptuple(ipglob) 

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

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

286 

287 def __getstate__(self): 

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

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

290 

291 def __setstate__(self, state): 

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

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

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

295 

296 def _get_glob(self): 

297 return self._glob 

298 

299 def _set_glob(self, ipglob): 

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

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

302 

303 glob = property(_get_glob, _set_glob, None, 

304 'an arbitrary IP address range in glob format.') 

305 

306 def __str__(self): 

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

308 return "%s" % self.glob 

309 

310 def __repr__(self): 

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

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