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

IpHandler::checkAllowedVersions()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

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