Passed
Pull Request — master (#320)
by Dmitriy
02:36
created

IpHandler::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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

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

105
        /** @scrutinizer ignore-unused */ ?ValidationContext $context

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
106
    ): Result {
107 86
        if ($cidr === null && $rule->isRequireSubnet()) {
108 4
            $result->addError(
109 4
                message: $rule->getNoSubnetMessage(),
110 4
                parameters: ['value' => $value]
111
            );
112 4
            return $result;
113
        }
114 82
        if ($cidr !== null && !$rule->isAllowSubnet()) {
115 4
            $result->addError(
116 4
                message: $rule->getHasSubnetMessage(),
117 4
                parameters: ['value' => $value]
118
            );
119 4
            return $result;
120
        }
121 78
        if ($negation && !$rule->isAllowNegation()) {
122 4
            $result->addError(
123 4
                message: $rule->getMessage(),
124 4
                parameters: ['value' => $value]
125
            );
126 4
            return $result;
127
        }
128 74
        return $result;
129
    }
130
131 74
    private function validateVersion(
132
        Ip $rule,
133
        Result $result,
134
        int $ipVersion,
135
        mixed $value,
136
        ?ValidationContext $context
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

136
        /** @scrutinizer ignore-unused */ ?ValidationContext $context

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
137
    ): Result {
138 74
        if ($ipVersion === IpHelper::IPV6 && !$rule->isAllowIpv6()) {
139 1
            $result->addError(
140 1
                message: $rule->getIpv6NotAllowedMessage(),
141 1
                parameters: ['value' => $value]
142
            );
143 1
            return $result;
144
        }
145 73
        if ($ipVersion === IpHelper::IPV4 && !$rule->isAllowIpv4()) {
146 2
            $result->addError(
147 2
                message: $rule->getIpv4NotAllowedMessage(),
148 2
                parameters: ['value' => $value]
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
        mixed $value,
161
        ?ValidationContext $context
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

161
        /** @scrutinizer ignore-unused */ ?ValidationContext $context

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
162
    ): Result {
163 71
        if ($cidr !== null) {
164
            try {
165 28
                IpHelper::getCidrBits($ipCidr);
166 2
            } catch (InvalidArgumentException $e) {
167 2
                $result->addError(
168 2
                    message: $rule->getWrongCidrMessage(),
169 2
                    parameters: ['value' => $value]
170
                );
171 2
                return $result;
172
            }
173
        }
174 69
        if (!$rule->isAllowed($ipCidr)) {
175 16
            $result->addError(
176 16
                message: $rule->getNotInRangeMessage(),
177 16
                parameters: ['value' => $value]
178
            );
179 16
            return $result;
180
        }
181 53
        return $result;
182
    }
183
}
184