Issues (37)

src/Range/Pattern.php (2 issues)

Labels
Severity
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
It seems like IPLib\Address\IPv4::parseString('0.0.0.0') can also be of type null; however, parameter $fromAddress 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 ignore-type  annotation

108
            return new static(/** @scrutinizer ignore-type */ IPv4::parseString('0.0.0.0'), IPv4::parseString('255.255.255.255'), 4);
Loading history...
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 ignore-type  annotation

108
            return new static(IPv4::parseString('0.0.0.0'), /** @scrutinizer ignore-type */ IPv4::parseString('255.255.255.255'), 4);
Loading history...
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