Passed
Pull Request — master (#222)
by Rustam
02:33
created

IpHandler::validateValueParts()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 15
ccs 11
cts 11
cp 1
rs 8.8333
cc 7
nc 4
nop 4
crap 7
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use InvalidArgumentException;
8
use RuntimeException;
9
use Yiisoft\NetworkUtilities\IpHelper;
10
use Yiisoft\Validator\Result;
11
use Yiisoft\Validator\ValidationContext;
12
use Yiisoft\Validator\Exception\UnexpectedRuleException;
13
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 IpHandler implements RuleHandlerInterface
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 118
    public function validate(mixed $value, object $rule, ?ValidationContext $context = null): Result
32
    {
33 118
        if (!$rule instanceof Ip) {
34 1
            throw new UnexpectedRuleException(Ip::class, $rule);
35
        }
36
37 117
        $this->checkAllowedVersions($rule);
38 116
        $result = new Result();
39 116
        if (!is_string($value)) {
40 4
            $result->addError($rule->getMessage());
41 4
            return $result;
42
        }
43
44 112
        if (preg_match($rule->getIpParsePattern(), $value, $matches) === 0) {
45 26
            $result->addError($rule->getMessage());
46 26
            return $result;
47
        }
48 86
        $negation = !empty($matches['not'] ?? null);
49 86
        $ip = $matches['ip'];
50 86
        $cidr = $matches['cidr'] ?? null;
51 86
        $ipCidr = $matches['ipCidr'];
52
53
        try {
54 86
            $ipVersion = IpHelper::getIpVersion($ip, false);
55
        } catch (InvalidArgumentException $e) {
56
            $result->addError($rule->getMessage());
57
            return $result;
58
        }
59
60 86
        $result = $this->validateValueParts($rule, $result, $cidr, $negation);
61 86
        if (!$result->isValid()) {
62 12
            return $result;
63
        }
64 74
        $result = $this->validateVersion($rule, $result, $ipVersion);
65 74
        if (!$result->isValid()) {
66 3
            return $result;
67
        }
68 71
        return $this->validateCidr($rule, $result, $cidr, $ipCidr);
69
    }
70
71
    /**
72
     * Used to get the Regexp pattern for initial IP address parsing.
73
     */
74
    public function getIpParsePattern(): string
75
    {
76
        return '/^(?<not>' . preg_quote(
77
            self::NEGATION_CHAR,
78
            '/'
79
        ) . ')?(?<ipCidr>(?<ip>(?:' . IpHelper::IPV4_PATTERN . ')|(?:' . IpHelper::IPV6_PATTERN . '))(?:\/(?<cidr>-?\d+))?)$/';
80
    }
81
82 117
    private function checkAllowedVersions(Ip $rule): void
83
    {
84 117
        if (!$rule->isAllowIpv4() && !$rule->isAllowIpv6()) {
85 1
            throw new RuntimeException('Both IPv4 and IPv6 checks can not be disabled at the same time.');
86
        }
87
    }
88
89 86
    private function validateValueParts(Ip $rule, Result $result, ?string $cidr, bool $negation): Result
90
    {
91 86
        if ($cidr === null && $rule->isRequireSubnet()) {
92 4
            $result->addError($rule->getNoSubnetMessage());
93 4
            return $result;
94
        }
95 82
        if ($cidr !== null && !$rule->isAllowSubnet()) {
96 4
            $result->addError($rule->getHasSubnetMessage());
97 4
            return $result;
98
        }
99 78
        if ($negation && !$rule->isAllowNegation()) {
100 4
            $result->addError($rule->getMessage());
101 4
            return $result;
102
        }
103 74
        return $result;
104
    }
105
106 74
    private function validateVersion(Ip $rule, Result $result, int $ipVersion): Result
107
    {
108 74
        if ($ipVersion === IpHelper::IPV6 && !$rule->isAllowIpv6()) {
109 1
            $result->addError($rule->getIpv6NotAllowedMessage());
110 1
            return $result;
111
        }
112 73
        if ($ipVersion === IpHelper::IPV4 && !$rule->isAllowIpv4()) {
113 2
            $result->addError($rule->getIpv4NotAllowedMessage());
114 2
            return $result;
115
        }
116 71
        return $result;
117
    }
118
119 71
    private function validateCidr(Ip $rule, Result $result, $cidr, $ipCidr): Result
120
    {
121 71
        if ($cidr !== null) {
122
            try {
123 28
                IpHelper::getCidrBits($ipCidr);
124 2
            } catch (InvalidArgumentException $e) {
125 2
                $result->addError($rule->getWrongCidrMessage());
126 2
                return $result;
127
            }
128
        }
129 69
        if (!$rule->isAllowed($ipCidr)) {
130 16
            $result->addError($rule->getNotInRangeMessage());
131 16
            return $result;
132
        }
133 53
        return $result;
134
    }
135
}
136