1 | <?php |
||||
2 | |||||
3 | namespace IPLib\Range; |
||||
4 | |||||
5 | use IPLib\Address\AddressInterface; |
||||
6 | use IPLib\Address\IPv4; |
||||
7 | use IPLib\Address\IPv6; |
||||
8 | use IPLib\Address\Type as AddressType; |
||||
9 | use IPLib\ParseStringFlag; |
||||
10 | |||||
11 | /** |
||||
12 | * Represents an address range in pattern format (only ending asterisks are supported). |
||||
13 | * |
||||
14 | * @example 127.0.*.* |
||||
15 | * @example ::/8 |
||||
16 | */ |
||||
17 | class Pattern extends AbstractRange |
||||
18 | { |
||||
19 | /** |
||||
20 | * Starting address of the range. |
||||
21 | * |
||||
22 | * @var \IPLib\Address\AddressInterface |
||||
23 | */ |
||||
24 | protected $fromAddress; |
||||
25 | |||||
26 | /** |
||||
27 | * Final address of the range. |
||||
28 | * |
||||
29 | * @var \IPLib\Address\AddressInterface |
||||
30 | */ |
||||
31 | protected $toAddress; |
||||
32 | |||||
33 | /** |
||||
34 | * Number of ending asterisks. |
||||
35 | * |
||||
36 | * @var int |
||||
37 | */ |
||||
38 | protected $asterisksCount; |
||||
39 | |||||
40 | /** |
||||
41 | * The type of the range of this IP range. |
||||
42 | * |
||||
43 | * @var int|false|null false if this range crosses multiple range types, null if yet to be determined |
||||
44 | * |
||||
45 | * @since 1.5.0 |
||||
46 | */ |
||||
47 | protected $rangeType; |
||||
48 | |||||
49 | /** |
||||
50 | * Initializes the instance. |
||||
51 | * |
||||
52 | * @param \IPLib\Address\AddressInterface $fromAddress |
||||
53 | * @param \IPLib\Address\AddressInterface $toAddress |
||||
54 | * @param int $asterisksCount |
||||
55 | */ |
||||
56 | 154 | public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $asterisksCount) |
|||
57 | { |
||||
58 | 154 | $this->fromAddress = $fromAddress; |
|||
59 | 154 | $this->toAddress = $toAddress; |
|||
60 | 154 | $this->asterisksCount = $asterisksCount; |
|||
61 | 154 | } |
|||
62 | |||||
63 | /** |
||||
64 | * {@inheritdoc} |
||||
65 | * |
||||
66 | * @see \IPLib\Range\RangeInterface::__toString() |
||||
67 | */ |
||||
68 | 70 | public function __toString() |
|||
69 | { |
||||
70 | 70 | return $this->toString(); |
|||
71 | } |
||||
72 | |||||
73 | /** |
||||
74 | * @deprecated since 1.17.0: use the parseString() method instead. |
||||
75 | * For upgrading: |
||||
76 | * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag |
||||
77 | * |
||||
78 | * @param string|mixed $range |
||||
79 | * @param bool $supportNonDecimalIPv4 |
||||
80 | * |
||||
81 | * @return static|null |
||||
82 | * |
||||
83 | * @see \IPLib\Range\Pattern::parseString() |
||||
84 | * @since 1.10.0 added the $supportNonDecimalIPv4 argument |
||||
85 | */ |
||||
86 | 12 | public static function fromString($range, $supportNonDecimalIPv4 = false) |
|||
87 | { |
||||
88 | 12 | return static::parseString($range, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0)); |
|||
89 | } |
||||
90 | |||||
91 | /** |
||||
92 | * Try get the range instance starting from its string representation. |
||||
93 | * |
||||
94 | * @param string|mixed $range |
||||
95 | * @param int $flags A combination or zero or more flags |
||||
96 | * |
||||
97 | * @return static|null |
||||
98 | * |
||||
99 | * @since 1.17.0 |
||||
100 | * @see \IPLib\ParseStringFlag |
||||
101 | */ |
||||
102 | 167 | public static function parseString($range, $flags = 0) |
|||
103 | { |
||||
104 | 167 | if (!is_string($range) || strpos($range, '*') === false) { |
|||
105 | 66 | return null; |
|||
106 | } |
||||
107 | 103 | if ($range === '*.*.*.*') { |
|||
108 | 5 | return new static(IPv4::parseString('0.0.0.0'), IPv4::parseString('255.255.255.255'), 4); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() It seems like
IPLib\Address\IPv4::pars...ring('255.255.255.255') can also be of type null ; however, parameter $toAddress of IPLib\Range\Pattern::__construct() does only seem to accept IPLib\Address\AddressInterface , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
109 | } |
||||
110 | 98 | if ($range === '*:*:*:*:*:*:*:*') { |
|||
111 | 3 | return new static(IPv6::parseString('::'), IPv6::parseString('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), 8); |
|||
112 | } |
||||
113 | 95 | $matches = null; |
|||
114 | 95 | if (strpos($range, '.') !== false && preg_match('/^[^*]+((?:\.\*)+)$/', $range, $matches)) { |
|||
115 | 58 | $asterisksCount = strlen($matches[1]) >> 1; |
|||
116 | 58 | if ($asterisksCount > 0) { |
|||
117 | 58 | $missingDots = 3 - substr_count($range, '.'); |
|||
118 | 58 | if ($missingDots > 0) { |
|||
119 | 2 | $range .= str_repeat('.*', $missingDots); |
|||
120 | 2 | $asterisksCount += $missingDots; |
|||
121 | } |
||||
122 | } |
||||
123 | 58 | $fromAddress = IPv4::parseString(str_replace('*', '0', $range), $flags); |
|||
124 | 58 | if ($fromAddress === null) { |
|||
125 | 1 | return null; |
|||
126 | } |
||||
127 | 57 | $fixedBytes = array_slice($fromAddress->getBytes(), 0, -$asterisksCount); |
|||
128 | 57 | $otherBytes = array_fill(0, $asterisksCount, 255); |
|||
129 | 57 | $toAddress = IPv4::fromBytes(array_merge($fixedBytes, $otherBytes)); |
|||
130 | |||||
131 | 57 | return new static($fromAddress, $toAddress, $asterisksCount); |
|||
132 | } |
||||
133 | 37 | if (strpos($range, ':') !== false && preg_match('/^[^*]+((?::\*)+)$/', $range, $matches)) { |
|||
134 | 33 | $asterisksCount = strlen($matches[1]) >> 1; |
|||
135 | 33 | $fromAddress = IPv6::parseString(str_replace('*', '0', $range)); |
|||
136 | 33 | if ($fromAddress === null) { |
|||
137 | return null; |
||||
138 | } |
||||
139 | 33 | $fixedWords = array_slice($fromAddress->getWords(), 0, -$asterisksCount); |
|||
140 | 33 | $otherWords = array_fill(0, $asterisksCount, 0xffff); |
|||
141 | 33 | $toAddress = IPv6::fromWords(array_merge($fixedWords, $otherWords)); |
|||
142 | |||||
143 | 33 | return new static($fromAddress, $toAddress, $asterisksCount); |
|||
144 | } |
||||
145 | |||||
146 | 4 | return null; |
|||
147 | } |
||||
148 | |||||
149 | /** |
||||
150 | * {@inheritdoc} |
||||
151 | * |
||||
152 | * @see \IPLib\Range\RangeInterface::toString() |
||||
153 | */ |
||||
154 | 115 | public function toString($long = false) |
|||
155 | { |
||||
156 | 115 | if ($this->asterisksCount === 0) { |
|||
157 | 56 | return $this->fromAddress->toString($long); |
|||
158 | } |
||||
159 | switch (true) { |
||||
160 | 71 | case $this->fromAddress instanceof \IPLib\Address\IPv4: |
|||
161 | 43 | $chunks = explode('.', $this->fromAddress->toString()); |
|||
162 | 43 | $chunks = array_slice($chunks, 0, -$this->asterisksCount); |
|||
163 | 43 | $chunks = array_pad($chunks, 4, '*'); |
|||
164 | 43 | $result = implode('.', $chunks); |
|||
165 | 43 | break; |
|||
166 | 28 | case $this->fromAddress instanceof \IPLib\Address\IPv6: |
|||
167 | 28 | if ($long) { |
|||
168 | 6 | $chunks = explode(':', $this->fromAddress->toString(true)); |
|||
169 | 6 | $chunks = array_slice($chunks, 0, -$this->asterisksCount); |
|||
170 | 6 | $chunks = array_pad($chunks, 8, '*'); |
|||
171 | 6 | $result = implode(':', $chunks); |
|||
172 | 28 | } elseif ($this->asterisksCount === 8) { |
|||
173 | 2 | $result = '*:*:*:*:*:*:*:*'; |
|||
174 | } else { |
||||
175 | 26 | $bytes = $this->toAddress->getBytes(); |
|||
176 | 26 | $bytes = array_slice($bytes, 0, -$this->asterisksCount * 2); |
|||
177 | 26 | $bytes = array_pad($bytes, 16, 1); |
|||
178 | 26 | $address = IPv6::fromBytes($bytes); |
|||
179 | 26 | $before = substr($address->toString(false), 0, -strlen(':101') * $this->asterisksCount); |
|||
180 | 26 | $result = $before . str_repeat(':*', $this->asterisksCount); |
|||
181 | } |
||||
182 | 28 | break; |
|||
183 | default: |
||||
184 | throw new \Exception('@todo'); // @codeCoverageIgnore |
||||
185 | } |
||||
186 | |||||
187 | 71 | return $result; |
|||
188 | } |
||||
189 | |||||
190 | /** |
||||
191 | * {@inheritdoc} |
||||
192 | * |
||||
193 | * @see \IPLib\Range\RangeInterface::getAddressType() |
||||
194 | */ |
||||
195 | 83 | public function getAddressType() |
|||
196 | { |
||||
197 | 83 | return $this->fromAddress->getAddressType(); |
|||
198 | } |
||||
199 | |||||
200 | /** |
||||
201 | * {@inheritdoc} |
||||
202 | * |
||||
203 | * @see \IPLib\Range\RangeInterface::getStartAddress() |
||||
204 | */ |
||||
205 | 37 | public function getStartAddress() |
|||
206 | { |
||||
207 | 37 | return $this->fromAddress; |
|||
208 | } |
||||
209 | |||||
210 | /** |
||||
211 | * {@inheritdoc} |
||||
212 | * |
||||
213 | * @see \IPLib\Range\RangeInterface::getEndAddress() |
||||
214 | */ |
||||
215 | 31 | public function getEndAddress() |
|||
216 | { |
||||
217 | 31 | return $this->toAddress; |
|||
218 | } |
||||
219 | |||||
220 | /** |
||||
221 | * {@inheritdoc} |
||||
222 | * |
||||
223 | * @see \IPLib\Range\RangeInterface::getComparableStartString() |
||||
224 | */ |
||||
225 | 41 | public function getComparableStartString() |
|||
226 | { |
||||
227 | 41 | return $this->fromAddress->getComparableString(); |
|||
228 | } |
||||
229 | |||||
230 | /** |
||||
231 | * {@inheritdoc} |
||||
232 | * |
||||
233 | * @see \IPLib\Range\RangeInterface::getComparableEndString() |
||||
234 | */ |
||||
235 | 41 | public function getComparableEndString() |
|||
236 | { |
||||
237 | 41 | return $this->toAddress->getComparableString(); |
|||
238 | } |
||||
239 | |||||
240 | /** |
||||
241 | * {@inheritdoc} |
||||
242 | * |
||||
243 | * @see \IPLib\Range\RangeInterface::asSubnet() |
||||
244 | * @since 1.8.0 |
||||
245 | */ |
||||
246 | 27 | public function asSubnet() |
|||
247 | { |
||||
248 | 27 | return new Subnet($this->getStartAddress(), $this->getEndAddress(), $this->getNetworkPrefix()); |
|||
249 | } |
||||
250 | |||||
251 | /** |
||||
252 | * {@inheritdoc} |
||||
253 | * |
||||
254 | * @see \IPLib\Range\RangeInterface::asPattern() |
||||
255 | */ |
||||
256 | 14 | public function asPattern() |
|||
257 | { |
||||
258 | 14 | return $this; |
|||
259 | } |
||||
260 | |||||
261 | /** |
||||
262 | * {@inheritdoc} |
||||
263 | * |
||||
264 | * @see \IPLib\Range\RangeInterface::getSubnetMask() |
||||
265 | */ |
||||
266 | 5 | public function getSubnetMask() |
|||
267 | { |
||||
268 | 5 | if ($this->getAddressType() !== AddressType::T_IPv4) { |
|||
269 | 1 | return null; |
|||
270 | } |
||||
271 | 4 | switch ($this->asterisksCount) { |
|||
272 | 4 | case 0: |
|||
273 | $bytes = array(255, 255, 255, 255); |
||||
274 | break; |
||||
275 | 4 | case 4: |
|||
276 | 1 | $bytes = array(0, 0, 0, 0); |
|||
277 | 1 | break; |
|||
278 | default: |
||||
279 | 3 | $bytes = array_pad(array_fill(0, 4 - $this->asterisksCount, 255), 4, 0); |
|||
280 | 3 | break; |
|||
281 | } |
||||
282 | |||||
283 | 4 | return IPv4::fromBytes($bytes); |
|||
284 | } |
||||
285 | |||||
286 | /** |
||||
287 | * {@inheritdoc} |
||||
288 | * |
||||
289 | * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName() |
||||
290 | */ |
||||
291 | 12 | public function getReverseDNSLookupName() |
|||
292 | { |
||||
293 | 12 | return $this->asterisksCount === 0 ? array($this->getStartAddress()->getReverseDNSLookupName()) : $this->asSubnet()->getReverseDNSLookupName(); |
|||
294 | } |
||||
295 | |||||
296 | /** |
||||
297 | * {@inheritdoc} |
||||
298 | * |
||||
299 | * @see \IPLib\Range\RangeInterface::getSize() |
||||
300 | */ |
||||
301 | 6 | public function getSize() |
|||
302 | { |
||||
303 | 6 | $fromAddress = $this->fromAddress; |
|||
304 | 6 | $maxPrefix = $fromAddress::getNumberOfBits(); |
|||
305 | 6 | $prefix = $this->getNetworkPrefix(); |
|||
306 | |||||
307 | 6 | return pow(2, ($maxPrefix - $prefix)); |
|||
308 | } |
||||
309 | |||||
310 | /** |
||||
311 | * {@inheritdoc} |
||||
312 | * |
||||
313 | * @see \IPLib\Range\RangeInterface::getNetworkPrefix() |
||||
314 | */ |
||||
315 | 43 | public function getNetworkPrefix() |
|||
316 | { |
||||
317 | 43 | switch ($this->getAddressType()) { |
|||
318 | case AddressType::T_IPv4: |
||||
319 | 20 | return 8 * (4 - $this->asterisksCount); |
|||
320 | case AddressType::T_IPv6: |
||||
321 | 23 | return 16 * (8 - $this->asterisksCount); |
|||
322 | } |
||||
323 | } |
||||
324 | } |
||||
325 |