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

IpHandler::validate()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 41
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 6

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 21
c 2
b 1
f 0
dl 0
loc 41
ccs 22
cts 22
cp 1
rs 8.9617
cc 6
nc 6
nop 3
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use InvalidArgumentException;
8
use Yiisoft\NetworkUtilities\IpHelper;
9
use Yiisoft\Validator\Exception\UnexpectedRuleException;
10
use Yiisoft\Validator\Result;
11
use Yiisoft\Validator\RuleHandlerInterface;
12
use Yiisoft\Validator\ValidationContext;
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 120
    public function validate(mixed $value, object $rule, ValidationContext $context): Result
32
    {
33 120
        if (!$rule instanceof Ip) {
34 1
            throw new UnexpectedRuleException(Ip::class, $rule);
35
        }
36
37 119
        $result = new Result();
38 119
        if (!is_string($value)) {
39 4
            return $result->addError($rule->getIncorrectInputMessage(), [
40 4
                'attribute' => $context->getAttribute(),
41 4
                'type' => get_debug_type($value),
42
            ]);
43
        }
44
45 115
        if (preg_match($this->getIpParsePattern(), $value, $matches) === 0) {
46 29
            return $result->addError($rule->getMessage(), ['attribute' => $context->getAttribute(), 'value' => $value]);
47
        }
48
49 86
        $negation = !empty($matches['not'] ?? null);
50 86
        $ip = $matches['ip'];
51 86
        $cidr = $matches['cidr'] ?? null;
52 86
        $ipCidr = $matches['ipCidr'];
53
        /**
54
         * Exception handling and validation in IpHelper are not needed because of the check above (regular expression
55
         * in "getIpParsePattern()").
56
         *
57
         * @infection-ignore-all
58
         */
59 86
        $ipVersion = IpHelper::getIpVersion($ip, validate: false);
60
61 86
        $result = $this->validateValueParts($rule, $result, $cidr, $negation, $value, $context);
62 86
        if (!$result->isValid()) {
63 12
            return $result;
64
        }
65
66 74
        $result = $this->validateVersion($rule, $result, $ipVersion, $value, $context);
67 74
        if (!$result->isValid()) {
68 3
            return $result;
69
        }
70
71 71
        return $this->validateCidr($rule, $result, $cidr, $ipCidr, $value, $context);
72
    }
73
74
    /**
75
     * Used to get the Regexp pattern for initial IP address parsing.
76
     */
77 115
    private function getIpParsePattern(): string
78
    {
79 115
        return '/^(?<not>' . preg_quote(
80
            self::NEGATION_CHAR,
81
            '/'
82
        ) . ')?(?<ipCidr>(?<ip>(?:' . IpHelper::IPV4_PATTERN . ')|(?:' . IpHelper::IPV6_PATTERN . '))(?:\/(?<cidr>-?\d+))?)$/';
83
    }
84
85 86
    private function validateValueParts(
86
        Ip $rule,
87
        Result $result,
88
        ?string $cidr,
89
        bool $negation,
90
        string $value,
91
        ValidationContext $context
92
    ): Result {
93 86
        if ($cidr === null && $rule->isRequireSubnet()) {
94 4
            $result->addError(
95 4
                $rule->getNoSubnetMessage(),
96
                [
97 4
                    'attribute' => $context->getAttribute(),
98
                    'value' => $value,
99
                ],
100
            );
101 4
            return $result;
102
        }
103 82
        if ($cidr !== null && !$rule->isAllowSubnet()) {
104 4
            $result->addError(
105 4
                $rule->getHasSubnetMessage(),
106
                [
107 4
                    'attribute' => $context->getAttribute(),
108
                    'value' => $value,
109
                ],
110
            );
111 4
            return $result;
112
        }
113 78
        if ($negation && !$rule->isAllowNegation()) {
114 4
            $result->addError(
115 4
                $rule->getMessage(),
116
                [
117 4
                    'attribute' => $context->getAttribute(),
118
                    'value' => $value,
119
                ],
120
            );
121 4
            return $result;
122
        }
123 74
        return $result;
124
    }
125
126 74
    private function validateVersion(
127
        Ip $rule,
128
        Result $result,
129
        int $ipVersion,
130
        string $value,
131
        ValidationContext $context
132
    ): Result {
133 74
        if ($ipVersion === IpHelper::IPV6 && !$rule->isAllowIpv6()) {
134 1
            $result->addError(
135 1
                $rule->getIpv6NotAllowedMessage(),
136
                [
137 1
                    'attribute' => $context->getAttribute(),
138
                    'value' => $value,
139
                ],
140
            );
141 1
            return $result;
142
        }
143 73
        if ($ipVersion === IpHelper::IPV4 && !$rule->isAllowIpv4()) {
144 2
            $result->addError(
145 2
                $rule->getIpv4NotAllowedMessage(),
146
                [
147 2
                    'attribute' => $context->getAttribute(),
148
                    'value' => $value,
149
                ],
150
            );
151 2
            return $result;
152
        }
153 71
        return $result;
154
    }
155
156 71
    private function validateCidr(
157
        Ip $rule,
158
        Result $result,
159
        ?string $cidr,
160
        string $ipCidr,
161
        string $value,
162
        ValidationContext $context
163
    ): Result {
164 71
        if ($cidr !== null) {
165
            try {
166 28
                IpHelper::getCidrBits($ipCidr);
167 2
            } catch (InvalidArgumentException) {
168 2
                $result->addError(
169 2
                    $rule->getWrongCidrMessage(),
170
                    [
171 2
                        'attribute' => $context->getAttribute(),
172
                        'value' => $value,
173
                    ],
174
                );
175 2
                return $result;
176
            }
177
        }
178 69
        if (!$rule->isAllowed($ipCidr)) {
179 16
            $result->addError(
180 16
                $rule->getNotInRangeMessage(),
181
                [
182 16
                    'attribute' => $context->getAttribute(),
183
                    'value' => $value,
184
                ],
185
            );
186 16
            return $result;
187
        }
188 53
        return $result;
189
    }
190
}
191