Passed
Pull Request — master (#364)
by
unknown
02:42
created

IpHandler::validate()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 42
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 7.025

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 25
c 2
b 0
f 0
dl 0
loc 42
ccs 23
cts 25
cp 0.92
rs 8.5866
cc 7
nc 7
nop 3
crap 7.025
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 118
    public function validate(mixed $value, object $rule, ValidationContext $context): Result
33
    {
34 118
        if (!$rule instanceof Ip) {
35 1
            throw new UnexpectedRuleException(Ip::class, $rule);
36
        }
37
38 117
        $this->checkAllowedVersions($rule);
39
40 116
        $result = new Result();
41 116
        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 112
        if (preg_match($rule->getIpParsePattern(), $value, $matches) === 0) {
49 26
            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
57
        try {
58 86
            $ipVersion = IpHelper::getIpVersion($ip, false);
59
        } catch (InvalidArgumentException) {
60
            return $result->addError($rule->getMessage(), ['attribute' => $context->getAttribute(), 'value' => $value]);
61
        }
62
63 86
        $result = $this->validateValueParts($rule, $result, $cidr, $negation, $value, $context);
64 86
        if (!$result->isValid()) {
65 12
            return $result;
66
        }
67
68 74
        $result = $this->validateVersion($rule, $result, $ipVersion, $value, $context);
69 74
        if (!$result->isValid()) {
70 3
            return $result;
71
        }
72
73 71
        return $this->validateCidr($rule, $result, $cidr, $ipCidr, $value, $context);
74
    }
75
76
    /**
77
     * Used to get the Regexp pattern for initial IP address parsing.
78
     */
79
    public function getIpParsePattern(): string
80
    {
81
        return '/^(?<not>' . preg_quote(
82
            self::NEGATION_CHAR,
83
            '/'
84
        ) . ')?(?<ipCidr>(?<ip>(?:' . IpHelper::IPV4_PATTERN . ')|(?:' . IpHelper::IPV6_PATTERN . '))(?:\/(?<cidr>-?\d+))?)$/';
85
    }
86
87 117
    private function checkAllowedVersions(Ip $rule): void
88
    {
89 117
        if (!$rule->isAllowIpv4() && !$rule->isAllowIpv6()) {
90 1
            throw new RuntimeException('Both IPv4 and IPv6 checks can not be disabled at the same time.');
91
        }
92
    }
93
94 86
    private function validateValueParts(
95
        Ip $rule,
96
        Result $result,
97
        ?string $cidr,
98
        bool $negation,
99
        string $value,
100
        ValidationContext $context
101
    ): Result {
102 86
        if ($cidr === null && $rule->isRequireSubnet()) {
103 4
            $result->addError(
104 4
                $rule->getNoSubnetMessage(),
105
                [
106 4
                    'attribute' => $context->getAttribute(),
107
                    'value' => $value,
108
                ],
109
            );
110 4
            return $result;
111
        }
112 82
        if ($cidr !== null && !$rule->isAllowSubnet()) {
113 4
            $result->addError(
114 4
                $rule->getHasSubnetMessage(),
115
                [
116 4
                    'attribute' => $context->getAttribute(),
117
                    'value' => $value,
118
                ],
119
            );
120 4
            return $result;
121
        }
122 78
        if ($negation && !$rule->isAllowNegation()) {
123 4
            $result->addError(
124 4
                $rule->getMessage(),
125
                [
126 4
                    'attribute' => $context->getAttribute(),
127
                    'value' => $value,
128
                ],
129
            );
130 4
            return $result;
131
        }
132 74
        return $result;
133
    }
134
135 74
    private function validateVersion(
136
        Ip $rule,
137
        Result $result,
138
        int $ipVersion,
139
        string $value,
140
        ValidationContext $context
141
    ): Result {
142 74
        if ($ipVersion === IpHelper::IPV6 && !$rule->isAllowIpv6()) {
143 1
            $result->addError(
144 1
                $rule->getIpv6NotAllowedMessage(),
145
                [
146 1
                    'attribute' => $context->getAttribute(),
147
                    'value' => $value,
148
                ],
149
            );
150 1
            return $result;
151
        }
152 73
        if ($ipVersion === IpHelper::IPV4 && !$rule->isAllowIpv4()) {
153 2
            $result->addError(
154 2
                $rule->getIpv4NotAllowedMessage(),
155
                [
156 2
                    'attribute' => $context->getAttribute(),
157
                    'value' => $value,
158
                ],
159
            );
160 2
            return $result;
161
        }
162 71
        return $result;
163
    }
164
165 71
    private function validateCidr(
166
        Ip $rule,
167
        Result $result,
168
        ?string $cidr,
169
        string $ipCidr,
170
        string $value,
171
        ValidationContext $context
172
    ): Result {
173 71
        if ($cidr !== null) {
174
            try {
175 28
                IpHelper::getCidrBits($ipCidr);
176 2
            } catch (InvalidArgumentException) {
177 2
                $result->addError(
178 2
                    $rule->getWrongCidrMessage(),
179
                    [
180 2
                        'attribute' => $context->getAttribute(),
181
                        'value' => $value,
182
                    ],
183
                );
184 2
                return $result;
185
            }
186
        }
187 69
        if (!$rule->isAllowed($ipCidr)) {
188 16
            $result->addError(
189 16
                $rule->getNotInRangeMessage(),
190
                [
191 16
                    'attribute' => $context->getAttribute(),
192
                    'value' => $value,
193
                ],
194
            );
195 16
            return $result;
196
        }
197 53
        return $result;
198
    }
199
}
200