Test Failed
Pull Request — master (#222)
by Rustam
14:22 queued 33s
created

IpHandler::getIpParsePattern()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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 function is_string;
13
use Yiisoft\Validator\Exception\UnexpectedRuleException;
14
15
/**
16
 * Checks if the value is a valid IPv4/IPv6 address or subnet.
17
 *
18
 * It also may change the value if normalization of IPv6 expansion is enabled.
19
 */
20
final class IpHandler implements RuleHandlerInterface
21
{
22
    /**
23
     * Negation char.
24
     *
25
     * Used to negate {@see $ranges} or {@see $network} or to negate validating value when {@see $allowNegation}
26
     * is used.
27
     */
28
    private const NEGATION_CHAR = '!';
29
30
    public function validate(mixed $value, object $rule, ?ValidationContext $context = null): Result
31
    {
32
        if (!$rule instanceof Ip) {
33
            throw new UnexpectedRuleException(Ip::class, $rule);
34
        }
35
36
        if (!$rule->allowIpv4 && !$rule->allowIpv6) {
37
            throw new RuntimeException('Both IPv4 and IPv6 checks can not be disabled at the same time.');
38
        }
39
        $result = new Result();
40
        if (!is_string($value)) {
41
            $result->addError($rule->message);
42
            return $result;
43
        }
44
45
        if (preg_match($rule->getIpParsePattern(), $value, $matches) === 0) {
46
            $result->addError($rule->message);
47
            return $result;
48
        }
49
        $negation = !empty($matches['not'] ?? null);
50
        $ip = $matches['ip'];
51
        $cidr = $matches['cidr'] ?? null;
52
        $ipCidr = $matches['ipCidr'];
53
54
        try {
55
            $ipVersion = IpHelper::getIpVersion($ip, false);
56
        } catch (InvalidArgumentException $e) {
57
            $result->addError($rule->message);
58
            return $result;
59
        }
60
61
        if ($rule->requireSubnet === true && $cidr === null) {
62
            $result->addError($rule->noSubnetMessage);
63
            return $result;
64
        }
65
        if ($rule->allowSubnet === false && $cidr !== null) {
66
            $result->addError($rule->hasSubnetMessage);
67
            return $result;
68
        }
69
        if ($rule->allowNegation === false && $negation) {
70
            $result->addError($rule->message);
71
            return $result;
72
        }
73
        if ($ipVersion === IpHelper::IPV6 && !$rule->allowIpv6) {
74
            $result->addError($rule->ipv6NotAllowedMessage);
75
            return $result;
76
        }
77
        if ($ipVersion === IpHelper::IPV4 && !$rule->allowIpv4) {
78
            $result->addError($rule->ipv4NotAllowedMessage);
79
            return $result;
80
        }
81
        if (!$result->isValid()) {
82
            return $result;
83
        }
84
        if ($cidr !== null) {
85
            try {
86
                IpHelper::getCidrBits($ipCidr);
87
            } catch (InvalidArgumentException $e) {
88
                $result->addError($rule->wrongCidrMessage);
89
                return $result;
90
            }
91
        }
92
        if (!$rule->isAllowed($ipCidr)) {
93
            $result->addError($rule->notInRangeMessage);
94
            return $result;
95
        }
96
97
        return $result;
98
    }
99
100
    /**
101
     * Used to get the Regexp pattern for initial IP address parsing.
102
     */
103
    public function getIpParsePattern(): string
104
    {
105
        return '/^(?<not>' . preg_quote(
106
            self::NEGATION_CHAR,
107
            '/'
108
        ) . ')?(?<ipCidr>(?<ip>(?:' . IpHelper::IPV4_PATTERN . ')|(?:' . IpHelper::IPV6_PATTERN . '))(?:\/(?<cidr>-?\d+))?)$/';
109
    }
110
}
111