Passed
Pull Request — master (#222)
by Alexander
04:47 queued 02:23
created

IpValidator   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 85
Duplicated Lines 0 %

Test Coverage

Coverage 86.96%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 50
c 1
b 0
f 0
dl 0
loc 85
ccs 40
cts 46
cp 0.8696
rs 10
wmc 21

2 Methods

Rating   Name   Duplication   Size   Complexity  
D validate() 0 64 20
A getIpParsePattern() 0 6 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule\Ip;
6
7
use InvalidArgumentException;
8
use RuntimeException;
9
use Yiisoft\NetworkUtilities\IpHelper;
10
use Yiisoft\Validator\Result;
11
use Yiisoft\Validator\Rule\RuleValidatorInterface;
12
use Yiisoft\Validator\ValidationContext;
13
use Yiisoft\Validator\ValidatorInterface;
14
use function is_string;
15
16
/**
17
 * Checks if the value is a valid IPv4/IPv6 address or subnet.
18
 *
19
 * It also may change the value if normalization of IPv6 expansion is enabled.
20
 */
21
final class IpValidator implements RuleValidatorInterface
22
{
23
    /**
24
     * Negation char.
25
     *
26
     * Used to negate {@see $ranges} or {@see $network} or to negate validating value when {@see $allowNegation}
27
     * is used.
28
     */
29
    private const NEGATION_CHAR = '!';
30
31 117
    public function validate(mixed $value, object $rule, ValidatorInterface $validator, ?ValidationContext $context = null): Result
32
    {
33 117
        if (!$rule->allowIpv4 && !$rule->allowIpv6) {
34 1
            throw new RuntimeException('Both IPv4 and IPv6 checks can not be disabled at the same time.');
35
        }
36 116
        $result = new Result();
37 116
        if (!is_string($value)) {
38 4
            $result->addError($rule->message);
39 4
            return $result;
40
        }
41
42 112
        if (preg_match($rule->getIpParsePattern(), $value, $matches) === 0) {
43 26
            $result->addError($rule->message);
44 26
            return $result;
45
        }
46 86
        $negation = !empty($matches['not'] ?? null);
47 86
        $ip = $matches['ip'];
48 86
        $cidr = $matches['cidr'] ?? null;
49 86
        $ipCidr = $matches['ipCidr'];
50
51
        try {
52 86
            $ipVersion = IpHelper::getIpVersion($ip, false);
53
        } catch (InvalidArgumentException $e) {
54
            $result->addError($rule->message);
55
            return $result;
56
        }
57
58 86
        if ($rule->requireSubnet === true && $cidr === null) {
59 4
            $result->addError($rule->noSubnetMessage);
60 4
            return $result;
61
        }
62 82
        if ($rule->allowSubnet === false && $cidr !== null) {
63 4
            $result->addError($rule->hasSubnetMessage);
64 4
            return $result;
65
        }
66 78
        if ($rule->allowNegation === false && $negation) {
67 4
            $result->addError($rule->message);
68 4
            return $result;
69
        }
70 74
        if ($ipVersion === IpHelper::IPV6 && !$rule->allowIpv6) {
71 1
            $result->addError($rule->ipv6NotAllowedMessage);
72 1
            return $result;
73
        }
74 73
        if ($ipVersion === IpHelper::IPV4 && !$rule->allowIpv4) {
75 2
            $result->addError($rule->ipv4NotAllowedMessage);
76 2
            return $result;
77
        }
78 71
        if (!$result->isValid()) {
79
            return $result;
80
        }
81 71
        if ($cidr !== null) {
82
            try {
83 28
                IpHelper::getCidrBits($ipCidr);
84 2
            } catch (InvalidArgumentException $e) {
85 2
                $result->addError($rule->wrongCidrMessage);
86 2
                return $result;
87
            }
88
        }
89 69
        if (!$rule->isAllowed($ipCidr)) {
90 16
            $result->addError($rule->notInRangeMessage);
91 16
            return $result;
92
        }
93
94 53
        return $result;
95
    }
96
97
    /**
98
     * Used to get the Regexp pattern for initial IP address parsing.
99
     */
100
    public function getIpParsePattern(): string
101
    {
102
        return '/^(?<not>' . preg_quote(
103
            self::NEGATION_CHAR,
104
            '/'
105
        ) . ')?(?<ipCidr>(?<ip>(?:' . IpHelper::IPV4_PATTERN . ')|(?:' . IpHelper::IPV6_PATTERN . '))(?:\/(?<cidr>-?\d+))?)$/';
106
    }
107
}
108