Passed
Pull Request — master (#222)
by Dmitriy
02:36
created

IpValidator   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 89
Duplicated Lines 0 %

Test Coverage

Coverage 87.5%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 22
eloc 52
c 1
b 0
f 0
dl 0
loc 89
ccs 42
cts 48
cp 0.875
rs 10

2 Methods

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