Passed
Push — master ( 5c2907...37dc07 )
by Sergei
24:35 queued 21:56
created

IpHandler::validate()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 43
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 7.0541

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 30
c 1
b 0
f 0
dl 0
loc 43
ccs 26
cts 29
cp 0.8966
rs 8.5066
cc 7
nc 7
nop 3
crap 7.0541
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 116
        $result = new Result();
40 116
        $message = $rule->getMessage();
41 116
        $messageParameters = [
42 116
            'attribute' => $context->getAttribute(),
43
            'value' => $value,
44
        ];
45 116
        if (!is_string($value)) {
46 4
            $result->addError($message, $messageParameters);
47 4
            return $result;
48
        }
49
50 112
        if (preg_match($rule->getIpParsePattern(), $value, $matches) === 0) {
51 26
            $result->addError($message, $messageParameters);
52 26
            return $result;
53
        }
54 86
        $negation = !empty($matches['not'] ?? null);
55 86
        $ip = $matches['ip'];
56 86
        $cidr = $matches['cidr'] ?? null;
57 86
        $ipCidr = $matches['ipCidr'];
58
59
        try {
60 86
            $ipVersion = IpHelper::getIpVersion($ip, false);
61
        } catch (InvalidArgumentException) {
62
            $result->addError($message, $messageParameters);
63
            return $result;
64
        }
65
66 86
        $result = $this->validateValueParts($rule, $result, $cidr, $negation, $value, $context);
67 86
        if (!$result->isValid()) {
68 12
            return $result;
69
        }
70 74
        $result = $this->validateVersion($rule, $result, $ipVersion, $value, $context);
71 74
        if (!$result->isValid()) {
72 3
            return $result;
73
        }
74 71
        return $this->validateCidr($rule, $result, $cidr, $ipCidr, $value, $context);
75
    }
76
77
    /**
78
     * Used to get the Regexp pattern for initial IP address parsing.
79
     */
80
    public function getIpParsePattern(): string
81
    {
82
        return '/^(?<not>' . preg_quote(
83
            self::NEGATION_CHAR,
84
            '/'
85
        ) . ')?(?<ipCidr>(?<ip>(?:' . IpHelper::IPV4_PATTERN . ')|(?:' . IpHelper::IPV6_PATTERN . '))(?:\/(?<cidr>-?\d+))?)$/';
86
    }
87
88 117
    private function checkAllowedVersions(Ip $rule): void
89
    {
90 117
        if (!$rule->isAllowIpv4() && !$rule->isAllowIpv6()) {
91 1
            throw new RuntimeException('Both IPv4 and IPv6 checks can not be disabled at the same time.');
92
        }
93
    }
94
95 86
    private function validateValueParts(
96
        Ip $rule,
97
        Result $result,
98
        ?string $cidr,
99
        bool $negation,
100
        mixed $value,
101
        ?ValidationContext $context
102
    ): Result {
103 86
        if ($cidr === null && $rule->isRequireSubnet()) {
104 4
            $result->addError(
105 4
                $rule->getNoSubnetMessage(),
106
                [
107 4
                    'attribute' => $context->getAttribute(),
0 ignored issues
show
Bug introduced by
The method getAttribute() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

107
                    'attribute' => $context->/** @scrutinizer ignore-call */ getAttribute(),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
108
                    'value' => $value,
109
                ],
110
            );
111 4
            return $result;
112
        }
113 82
        if ($cidr !== null && !$rule->isAllowSubnet()) {
114 4
            $result->addError(
115 4
                $rule->getHasSubnetMessage(),
116
                [
117 4
                    'attribute' => $context->getAttribute(),
118
                    'value' => $value,
119
                ],
120
            );
121 4
            return $result;
122
        }
123 78
        if ($negation && !$rule->isAllowNegation()) {
124 4
            $result->addError(
125 4
                $rule->getMessage(),
126
                [
127 4
                    'attribute' => $context->getAttribute(),
128
                    'value' => $value,
129
                ],
130
            );
131 4
            return $result;
132
        }
133 74
        return $result;
134
    }
135
136 74
    private function validateVersion(
137
        Ip $rule,
138
        Result $result,
139
        int $ipVersion,
140
        mixed $value,
141
        ValidationContext $context
142
    ): Result {
143 74
        if ($ipVersion === IpHelper::IPV6 && !$rule->isAllowIpv6()) {
144 1
            $result->addError(
145 1
                $rule->getIpv6NotAllowedMessage(),
146
                [
147 1
                    'attribute' => $context->getAttribute(),
148
                    'value' => $value,
149
                ],
150
            );
151 1
            return $result;
152
        }
153 73
        if ($ipVersion === IpHelper::IPV4 && !$rule->isAllowIpv4()) {
154 2
            $result->addError(
155 2
                $rule->getIpv4NotAllowedMessage(),
156
                [
157 2
                    'attribute' => $context->getAttribute(),
158
                    'value' => $value,
159
                ],
160
            );
161 2
            return $result;
162
        }
163 71
        return $result;
164
    }
165
166 71
    private function validateCidr(
167
        Ip $rule,
168
        Result $result,
169
        ?string $cidr,
170
        string $ipCidr,
171
        mixed $value,
172
        ValidationContext $context
173
    ): Result {
174 71
        if ($cidr !== null) {
175
            try {
176 28
                IpHelper::getCidrBits($ipCidr);
177 2
            } catch (InvalidArgumentException) {
178 2
                $result->addError(
179 2
                    $rule->getWrongCidrMessage(),
180
                    [
181 2
                        'attribute' => $context->getAttribute(),
182
                        'value' => $value,
183
                    ],
184
                );
185 2
                return $result;
186
            }
187
        }
188 69
        if (!$rule->isAllowed($ipCidr)) {
189 16
            $result->addError(
190 16
                $rule->getNotInRangeMessage(),
191
                [
192 16
                    'attribute' => $context->getAttribute(),
193
                    'value' => $value,
194
                ],
195
            );
196 16
            return $result;
197
        }
198 53
        return $result;
199
    }
200
}
201