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

IpHandler   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 167
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 23
eloc 74
c 1
b 0
f 0
dl 0
loc 167
ccs 67
cts 67
cp 1
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
B validate() 0 40 6
B validateValueParts() 0 39 7
A validateVersion() 0 28 5
A validateCidr() 0 33 4
A getIpParsePattern() 0 6 1
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
         * @infection-ignore-all
57
         */
58 86
        $ipVersion = IpHelper::getIpVersion($ip, validate: false);
59
60 86
        $result = $this->validateValueParts($rule, $result, $cidr, $negation, $value, $context);
61 86
        if (!$result->isValid()) {
62 12
            return $result;
63
        }
64
65 74
        $result = $this->validateVersion($rule, $result, $ipVersion, $value, $context);
66 74
        if (!$result->isValid()) {
67 3
            return $result;
68
        }
69
70 71
        return $this->validateCidr($rule, $result, $cidr, $ipCidr, $value, $context);
71
    }
72
73
    /**
74
     * Used to get the Regexp pattern for initial IP address parsing.
75
     */
76 115
    private function getIpParsePattern(): string
77
    {
78 115
        return '/^(?<not>' . preg_quote(
79
            self::NEGATION_CHAR,
80
            '/'
81
        ) . ')?(?<ipCidr>(?<ip>(?:' . IpHelper::IPV4_PATTERN . ')|(?:' . IpHelper::IPV6_PATTERN . '))(?:\/(?<cidr>-?\d+))?)$/';
82
    }
83
84 86
    private function validateValueParts(
85
        Ip $rule,
86
        Result $result,
87
        ?string $cidr,
88
        bool $negation,
89
        string $value,
90
        ValidationContext $context
91
    ): Result {
92 86
        if ($cidr === null && $rule->isRequireSubnet()) {
93 4
            $result->addError(
94 4
                $rule->getNoSubnetMessage(),
95
                [
96 4
                    'attribute' => $context->getAttribute(),
97
                    'value' => $value,
98
                ],
99
            );
100 4
            return $result;
101
        }
102 82
        if ($cidr !== null && !$rule->isAllowSubnet()) {
103 4
            $result->addError(
104 4
                $rule->getHasSubnetMessage(),
105
                [
106 4
                    'attribute' => $context->getAttribute(),
107
                    'value' => $value,
108
                ],
109
            );
110 4
            return $result;
111
        }
112 78
        if ($negation && !$rule->isAllowNegation()) {
113 4
            $result->addError(
114 4
                $rule->getMessage(),
115
                [
116 4
                    'attribute' => $context->getAttribute(),
117
                    'value' => $value,
118
                ],
119
            );
120 4
            return $result;
121
        }
122 74
        return $result;
123
    }
124
125 74
    private function validateVersion(
126
        Ip $rule,
127
        Result $result,
128
        int $ipVersion,
129
        string $value,
130
        ValidationContext $context
131
    ): Result {
132 74
        if ($ipVersion === IpHelper::IPV6 && !$rule->isAllowIpv6()) {
133 1
            $result->addError(
134 1
                $rule->getIpv6NotAllowedMessage(),
135
                [
136 1
                    'attribute' => $context->getAttribute(),
137
                    'value' => $value,
138
                ],
139
            );
140 1
            return $result;
141
        }
142 73
        if ($ipVersion === IpHelper::IPV4 && !$rule->isAllowIpv4()) {
143 2
            $result->addError(
144 2
                $rule->getIpv4NotAllowedMessage(),
145
                [
146 2
                    'attribute' => $context->getAttribute(),
147
                    'value' => $value,
148
                ],
149
            );
150 2
            return $result;
151
        }
152 71
        return $result;
153
    }
154
155 71
    private function validateCidr(
156
        Ip $rule,
157
        Result $result,
158
        ?string $cidr,
159
        string $ipCidr,
160
        string $value,
161
        ValidationContext $context
162
    ): Result {
163 71
        if ($cidr !== null) {
164
            try {
165 28
                IpHelper::getCidrBits($ipCidr);
166 2
            } catch (InvalidArgumentException) {
167 2
                $result->addError(
168 2
                    $rule->getWrongCidrMessage(),
169
                    [
170 2
                        'attribute' => $context->getAttribute(),
171
                        'value' => $value,
172
                    ],
173
                );
174 2
                return $result;
175
            }
176
        }
177 69
        if (!$rule->isAllowed($ipCidr)) {
178 16
            $result->addError(
179 16
                $rule->getNotInRangeMessage(),
180
                [
181 16
                    'attribute' => $context->getAttribute(),
182
                    'value' => $value,
183
                ],
184
            );
185 16
            return $result;
186
        }
187 53
        return $result;
188
    }
189
}
190