Passed
Pull Request — master (#369)
by
unknown
02:46
created

IpHandler::validateCidr()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 33
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 16
c 1
b 0
f 0
dl 0
loc 33
ccs 14
cts 14
cp 1
rs 9.7333
cc 4
nc 5
nop 6
crap 4
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\Exception\UnexpectedRuleException;
11
use Yiisoft\Validator\Result;
12
use Yiisoft\Validator\RuleHandlerInterface;
13
use Yiisoft\Validator\ValidationContext;
14
15
use function is_string;
16
17
/**
18
 * Checks if the value is a valid IPv4/IPv6 address or subnet.
19
 *
20
 * It also may change the value if normalization of IPv6 expansion is enabled.
21
 */
22
final class IpHandler implements RuleHandlerInterface
23
{
24
    /**
25
     * Negation char.
26
     *
27
     * Used to negate {@see $ranges} or {@see $network} or to negate validating value when {@see $allowNegation}
28
     * is used.
29
     */
30
    private const NEGATION_CHAR = '!';
31
32 121
    public function validate(mixed $value, object $rule, ValidationContext $context): Result
33
    {
34 121
        if (!$rule instanceof Ip) {
35 1
            throw new UnexpectedRuleException(Ip::class, $rule);
36
        }
37
38 120
        $this->checkAllowedVersions($rule);
39
40 119
        $result = new Result();
41 119
        if (!is_string($value)) {
42 4
            return $result->addError($rule->getIncorrectInputMessage(), [
43 4
                'attribute' => $context->getAttribute(),
44 4
                'type' => get_debug_type($value),
45
            ]);
46
        }
47
48 115
        if (preg_match($this->getIpParsePattern(), $value, $matches) === 0) {
49 29
            return $result->addError($rule->getMessage(), ['attribute' => $context->getAttribute(), 'value' => $value]);
50
        }
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
        // Exception can not be thrown here because of the check above (regular expression in "getIpParsePattern()").
57 86
        $ipVersion = IpHelper::getIpVersion($ip, validate: false);
58
59 86
        $result = $this->validateValueParts($rule, $result, $cidr, $negation, $value, $context);
60 86
        if (!$result->isValid()) {
61 12
            return $result;
62
        }
63
64 74
        $result = $this->validateVersion($rule, $result, $ipVersion, $value, $context);
65 74
        if (!$result->isValid()) {
66 3
            return $result;
67
        }
68
69 71
        return $this->validateCidr($rule, $result, $cidr, $ipCidr, $value, $context);
70
    }
71
72
    /**
73
     * Used to get the Regexp pattern for initial IP address parsing.
74
     */
75 115
    private function getIpParsePattern(): string
76
    {
77 115
        return '/^(?<not>' . preg_quote(
78
            self::NEGATION_CHAR,
79
            '/'
80
        ) . ')?(?<ipCidr>(?<ip>(?:' . IpHelper::IPV4_PATTERN . ')|(?:' . IpHelper::IPV6_PATTERN . '))(?:\/(?<cidr>-?\d+))?)$/';
81
    }
82
83 120
    private function checkAllowedVersions(Ip $rule): void
84
    {
85 120
        if (!$rule->isAllowIpv4() && !$rule->isAllowIpv6()) {
86 1
            throw new RuntimeException('Both IPv4 and IPv6 checks can not be disabled at the same time.');
87
        }
88
    }
89
90 86
    private function validateValueParts(
91
        Ip $rule,
92
        Result $result,
93
        ?string $cidr,
94
        bool $negation,
95
        string $value,
96
        ValidationContext $context
97
    ): Result {
98 86
        if ($cidr === null && $rule->isRequireSubnet()) {
99 4
            $result->addError(
100 4
                $rule->getNoSubnetMessage(),
101
                [
102 4
                    'attribute' => $context->getAttribute(),
103
                    'value' => $value,
104
                ],
105
            );
106 4
            return $result;
107
        }
108 82
        if ($cidr !== null && !$rule->isAllowSubnet()) {
109 4
            $result->addError(
110 4
                $rule->getHasSubnetMessage(),
111
                [
112 4
                    'attribute' => $context->getAttribute(),
113
                    'value' => $value,
114
                ],
115
            );
116 4
            return $result;
117
        }
118 78
        if ($negation && !$rule->isAllowNegation()) {
119 4
            $result->addError(
120 4
                $rule->getMessage(),
121
                [
122 4
                    'attribute' => $context->getAttribute(),
123
                    'value' => $value,
124
                ],
125
            );
126 4
            return $result;
127
        }
128 74
        return $result;
129
    }
130
131 74
    private function validateVersion(
132
        Ip $rule,
133
        Result $result,
134
        int $ipVersion,
135
        string $value,
136
        ValidationContext $context
137
    ): Result {
138 74
        if ($ipVersion === IpHelper::IPV6 && !$rule->isAllowIpv6()) {
139 1
            $result->addError(
140 1
                $rule->getIpv6NotAllowedMessage(),
141
                [
142 1
                    'attribute' => $context->getAttribute(),
143
                    'value' => $value,
144
                ],
145
            );
146 1
            return $result;
147
        }
148 73
        if ($ipVersion === IpHelper::IPV4 && !$rule->isAllowIpv4()) {
149 2
            $result->addError(
150 2
                $rule->getIpv4NotAllowedMessage(),
151
                [
152 2
                    'attribute' => $context->getAttribute(),
153
                    'value' => $value,
154
                ],
155
            );
156 2
            return $result;
157
        }
158 71
        return $result;
159
    }
160
161 71
    private function validateCidr(
162
        Ip $rule,
163
        Result $result,
164
        ?string $cidr,
165
        string $ipCidr,
166
        string $value,
167
        ValidationContext $context
168
    ): Result {
169 71
        if ($cidr !== null) {
170
            try {
171 28
                IpHelper::getCidrBits($ipCidr);
172 2
            } catch (InvalidArgumentException) {
173 2
                $result->addError(
174 2
                    $rule->getWrongCidrMessage(),
175
                    [
176 2
                        'attribute' => $context->getAttribute(),
177
                        'value' => $value,
178
                    ],
179
                );
180 2
                return $result;
181
            }
182
        }
183 69
        if (!$rule->isAllowed($ipCidr)) {
184 16
            $result->addError(
185 16
                $rule->getNotInRangeMessage(),
186
                [
187 16
                    'attribute' => $context->getAttribute(),
188
                    'value' => $value,
189
                ],
190
            );
191 16
            return $result;
192
        }
193 53
        return $result;
194
    }
195
}
196