Passed
Pull Request — master (#293)
by
unknown
02:21
created

IpHandler   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 166
Duplicated Lines 0 %

Test Coverage

Coverage 94.12%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 28
eloc 84
c 1
b 0
f 0
dl 0
loc 166
ccs 80
cts 85
cp 0.9412
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A checkAllowedVersions() 0 4 3
B validate() 0 42 7
B validateValueParts() 0 33 7
A validateVersion() 0 24 5
A __construct() 0 2 1
A validateCidr() 0 29 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 RuntimeException;
9
use Yiisoft\NetworkUtilities\IpHelper;
10
use Yiisoft\Translator\TranslatorInterface;
11
use Yiisoft\Validator\Exception\UnexpectedRuleException;
12
use Yiisoft\Validator\Result;
13
use Yiisoft\Validator\RuleHandlerInterface;
14
use Yiisoft\Validator\ValidationContext;
15
16
use function is_string;
17
18
/**
19
 * Checks if the value is a valid IPv4/IPv6 address or subnet.
20
 *
21
 * It also may change the value if normalization of IPv6 expansion is enabled.
22
 */
23
final class IpHandler implements RuleHandlerInterface
24
{
25 119
    public function __construct(private TranslatorInterface $translator)
26
    {
27
    }
28
29
    /**
30
     * Negation char.
31
     *
32
     * Used to negate {@see $ranges} or {@see $network} or to negate validating value when {@see $allowNegation}
33
     * is used.
34
     */
35
    private const NEGATION_CHAR = '!';
36
37 118
    public function validate(mixed $value, object $rule, ValidationContext $context): Result
38
    {
39 118
        if (!$rule instanceof Ip) {
40 1
            throw new UnexpectedRuleException(Ip::class, $rule);
41
        }
42
43 117
        $this->checkAllowedVersions($rule);
44 116
        $result = new Result();
45 116
        $formattedMessage = $this->translator->translate(
46 116
            $rule->getMessage(),
47 116
            ['attribute' => $context->getAttribute(), 'value' => $value]
48
        );
49 116
        if (!is_string($value)) {
50 4
            $result->addError($formattedMessage);
51 4
            return $result;
52
        }
53
54 112
        if (preg_match($rule->getIpParsePattern(), $value, $matches) === 0) {
55 26
            $result->addError($formattedMessage);
56 26
            return $result;
57
        }
58 86
        $negation = !empty($matches['not'] ?? null);
59 86
        $ip = $matches['ip'];
60 86
        $cidr = $matches['cidr'] ?? null;
61 86
        $ipCidr = $matches['ipCidr'];
62
63
        try {
64 86
            $ipVersion = IpHelper::getIpVersion($ip, false);
65
        } catch (InvalidArgumentException $e) {
66
            $result->addError($formattedMessage);
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
    public function getIpParsePattern(): string
85
    {
86
        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
106
    ): Result {
107 86
        if ($cidr === null && $rule->isRequireSubnet()) {
108 4
            $formattedMessage = $this->translator->translate(
109 4
                $rule->getNoSubnetMessage(),
110 4
                ['attribute' => $context->getAttribute(), 'value' => $value]
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

110
                ['attribute' => $context->/** @scrutinizer ignore-call */ getAttribute(), 'value' => $value]

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