Passed
Pull Request — master (#222)
by Dmitriy
02:23
created

IpHandler   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 89
Duplicated Lines 0 %

Test Coverage

Coverage 87.5%

Importance

Changes 0
Metric Value
wmc 22
eloc 52
dl 0
loc 89
ccs 42
cts 48
cp 0.875
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A getIpParsePattern() 0 6 1
D validate() 0 68 21
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\Result;
11
use Yiisoft\Validator\ValidationContext;
12
use Yiisoft\Validator\ValidatorInterface;
13
use function is_string;
14
use Yiisoft\Validator\Exception\UnexpectedRuleException;
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 118
    public function validate(mixed $value, object $rule, ?ValidationContext $context = null): Result
32
    {
33 118
        if (!$rule instanceof Ip) {
34 1
            throw new UnexpectedRuleException(Ip::class, $rule);
35
        }
36
37 117
        if (!$rule->allowIpv4 && !$rule->allowIpv6) {
38 1
            throw new RuntimeException('Both IPv4 and IPv6 checks can not be disabled at the same time.');
39
        }
40 116
        $result = new Result();
41 116
        if (!is_string($value)) {
42 4
            $result->addError($rule->message);
43 4
            return $result;
44
        }
45
46 112
        if (preg_match($rule->getIpParsePattern(), $value, $matches) === 0) {
47 26
            $result->addError($rule->message);
48 26
            return $result;
49
        }
50 86
        $negation = !empty($matches['not'] ?? null);
51 86
        $ip = $matches['ip'];
52 86
        $cidr = $matches['cidr'] ?? null;
53 86
        $ipCidr = $matches['ipCidr'];
54
55
        try {
56 86
            $ipVersion = IpHelper::getIpVersion($ip, false);
57
        } catch (InvalidArgumentException $e) {
58
            $result->addError($rule->message);
59
            return $result;
60
        }
61
62 86
        if ($rule->requireSubnet === true && $cidr === null) {
63 4
            $result->addError($rule->noSubnetMessage);
64 4
            return $result;
65
        }
66 82
        if ($rule->allowSubnet === false && $cidr !== null) {
67 4
            $result->addError($rule->hasSubnetMessage);
68 4
            return $result;
69
        }
70 78
        if ($rule->allowNegation === false && $negation) {
71 4
            $result->addError($rule->message);
72 4
            return $result;
73
        }
74 74
        if ($ipVersion === IpHelper::IPV6 && !$rule->allowIpv6) {
75 1
            $result->addError($rule->ipv6NotAllowedMessage);
76 1
            return $result;
77
        }
78 73
        if ($ipVersion === IpHelper::IPV4 && !$rule->allowIpv4) {
79 2
            $result->addError($rule->ipv4NotAllowedMessage);
80 2
            return $result;
81
        }
82 71
        if (!$result->isValid()) {
83
            return $result;
84
        }
85 71
        if ($cidr !== null) {
86
            try {
87 28
                IpHelper::getCidrBits($ipCidr);
88 2
            } catch (InvalidArgumentException $e) {
89 2
                $result->addError($rule->wrongCidrMessage);
90 2
                return $result;
91
            }
92
        }
93 69
        if (!$rule->isAllowed($ipCidr)) {
94 16
            $result->addError($rule->notInRangeMessage);
95 16
            return $result;
96
        }
97
98 53
        return $result;
99
    }
100
101
    /**
102
     * Used to get the Regexp pattern for initial IP address parsing.
103
     */
104
    public function getIpParsePattern(): string
105
    {
106
        return '/^(?<not>' . preg_quote(
107
            self::NEGATION_CHAR,
108
            '/'
109
        ) . ')?(?<ipCidr>(?<ip>(?:' . IpHelper::IPV4_PATTERN . ')|(?:' . IpHelper::IPV6_PATTERN . '))(?:\/(?<cidr>-?\d+))?)$/';
110
    }
111
}
112