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
« 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.
10"""
11from netaddr.core import AddrFormatError, AddrConversionError
12from netaddr.ip import IPRange, IPAddress, IPNetwork, iprange_to_cidrs
13from netaddr.compat import _is_str
16def valid_glob(ipglob):
17 """
18 :param ipglob: An IP address range in a glob-style format.
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
29 seen_hyphen = False
30 seen_asterisk = False
32 octets = ipglob.split('.')
34 if len(octets) != 4:
35 return False
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
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.
75 :param ipglob: an IP address range in a glob-style format.
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,))
82 start_tokens = []
83 end_tokens = []
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)
97 return IPAddress('.'.join(start_tokens)), IPAddress('.'.join(end_tokens))
100def glob_to_iprange(ipglob):
101 """
102 A function that accepts a glob-style IP range and returns the equivalent
103 IP range.
105 :param ipglob: an IP address range in a glob-style format.
107 :return: an IPRange object.
108 """
109 if not valid_glob(ipglob):
110 raise AddrFormatError('not a recognised IP glob range: %r!' % (ipglob,))
112 start_tokens = []
113 end_tokens = []
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)
127 return IPRange('.'.join(start_tokens), '.'.join(end_tokens))
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.
135 :param start: the start IP address or subnet.
137 :param end: the end IP address or subnet.
139 :return: a list containing one or more IP globs.
140 """
141 start = IPAddress(start)
142 end = IPAddress(end)
144 if start.version != 4 and end.version != 4:
145 raise AddrConversionError('IP glob ranges only support IPv4!')
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('.')]
152 tokens = []
154 seen_hyphen = False
155 seen_asterisk = False
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!")
178 return '.'.join(tokens)
180 globs = []
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)
201 return globs
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.
209 :param ipglob: an IP address range in a glob-style format.
211 :return: a list of one or more IP objects.
212 """
213 return iprange_to_cidrs(*glob_to_iptuple(ipglob))
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.
221 :param cidr: an IP object CIDR subnet.
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]
234class IPGlob(IPRange):
235 """
236 Represents an IP address range using a glob-style syntax ``x.x.x-y.*``
238 Individual octets can be represented using the following shortcuts :
240 1. ``*`` - the asterisk octet (represents values ``0`` through ``255``)
241 2. ``x-y`` - the hyphenated octet (represents values ``x`` through ``y``)
243 A few basic rules also apply :
245 1. ``x`` must always be less than ``y``, therefore :
247 - ``x`` can only be ``0`` through ``254``
248 - ``y`` can only be ``1`` through ``255``
250 2. only one hyphenated octet per IP glob is allowed
251 3. only asterisks are permitted after a hyphenated octet
253 Examples:
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 +------------------+------------------------------+
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',)
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]
287 def __getstate__(self):
288 """:return: Pickled state of an `IPGlob` object."""
289 return super(IPGlob, self).__getstate__()
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]
296 def _get_glob(self):
297 return self._glob
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]
303 glob = property(_get_glob, _set_glob, None,
304 'an arbitrary IP address range in glob format.')
306 def __str__(self):
307 """:return: IP glob in common representational format."""
308 return "%s" % self.glob
310 def __repr__(self):
311 """:return: Python statement to create an equivalent object"""
312 return "%s('%s')" % (self.__class__.__name__, self.glob)