Passed
Push — master ( a36249...470c38 )
by
unknown
11:22 queued 08:46
created

IpHandler::getGenericErrorResult()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 3
crap 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 135
    public function validate(mixed $value, object $rule, ValidationContext $context): Result
32
    {
33 135
        if (!$rule instanceof Ip) {
34 1
            throw new UnexpectedRuleException(Ip::class, $rule);
35
        }
36
37 134
        if (!is_string($value)) {
38 8
            return (new Result())->addError($rule->getIncorrectInputMessage(), [
39 8
                'attribute' => $context->getAttribute(),
40 8
                'type' => get_debug_type($value),
41
            ]);
42
        }
43
44 126
        if (preg_match(self::getIpParsePattern(), $value, $matches) === 0) {
45 26
            return self::getGenericErrorResult($rule->getMessage(), $context, $value);
46
        }
47
48 100
        $negation = !empty($matches['not'] ?? null);
49 100
        $ip = $matches['ip'];
50 100
        $cidr = $matches['cidr'] ?? null;
51 100
        $ipCidr = $matches['ipCidr'];
52
        /**
53
         * Exception handling and validation in IpHelper are not needed because of the check above (regular expression
54
         * in "getIpParsePattern()").
55
         *
56
         * @infection-ignore-all
57
         */
58 100
        $ipVersion = IpHelper::getIpVersion($ip, validate: false);
59
60 100
        $result = self::validateValueParts($rule, $cidr, $negation, $value, $context);
61 100
        if ($result !== null) {
62 15
            return $result;
63
        }
64
65 85
        $result = self::validateVersion($rule, $ipVersion, $value, $context);
66 85
        if ($result !== null) {
67 8
            return $result;
68
        }
69
70 77
        $result = self::validateCidr($rule, $cidr, $ipCidr, $value, $context);
71 77
        if ($result !== null) {
72 24
            return $result;
73
        }
74
75 53
        return new Result();
76
    }
77
78
    /**
79
     * Used to get the Regexp pattern for initial IP address parsing.
80
     */
81 126
    private static function getIpParsePattern(): string
82
    {
83
        return '/^(?<not>' .
84
            self::NEGATION_CHAR .
85 126
            ')?(?<ipCidr>(?<ip>(?:' . IpHelper::IPV4_PATTERN . ')|(?:' . IpHelper::IPV6_PATTERN . '))(?:\/(?<cidr>-?\d+))?)$/';
86
    }
87
88 100
    private static function validateValueParts(
89
        Ip $rule,
90
        ?string $cidr,
91
        bool $negation,
92
        string $value,
93
        ValidationContext $context
94
    ): Result|null {
95 100
        if ($cidr === null && $rule->isRequireSubnet()) {
96 7
            return self::getGenericErrorResult($rule->getNoSubnetMessage(), $context, $value);
97
        }
98
99 93
        if ($cidr !== null && !$rule->isAllowSubnet()) {
100 5
            return self::getGenericErrorResult($rule->getHasSubnetMessage(), $context, $value);
101
        }
102
103 88
        if ($negation && !$rule->isAllowNegation()) {
104 3
            return self::getGenericErrorResult($rule->getMessage(), $context, $value);
105
        }
106
107 85
        return null;
108
    }
109
110 85
    private static function validateVersion(
111
        Ip $rule,
112
        int $ipVersion,
113
        string $value,
114
        ValidationContext $context
115
    ): Result|null {
116 85
        if ($ipVersion === IpHelper::IPV6 && !$rule->isAllowIpv6()) {
117 4
            return self::getGenericErrorResult($rule->getIpv6NotAllowedMessage(), $context, $value);
118
        }
119
120 81
        if ($ipVersion === IpHelper::IPV4 && !$rule->isAllowIpv4()) {
121 4
            return self::getGenericErrorResult($rule->getIpv4NotAllowedMessage(), $context, $value);
122
        }
123
124 77
        return null;
125
    }
126
127 77
    private static function validateCidr(
128
        Ip $rule,
129
        ?string $cidr,
130
        string $ipCidr,
131
        string $value,
132
        ValidationContext $context
133
    ): Result|null {
134 77
        if ($cidr !== null) {
135
            try {
136 31
                IpHelper::getCidrBits($ipCidr);
137 5
            } catch (InvalidArgumentException) {
138 5
                return self::getGenericErrorResult($rule->getWrongCidrMessage(), $context, $value);
139
            }
140
        }
141
142 72
        if (!$rule->isAllowed($ipCidr)) {
143 19
            return self::getGenericErrorResult($rule->getNotInRangeMessage(), $context, $value);
144
        }
145
146 53
        return null;
147
    }
148
149 73
    private static function getGenericErrorResult(string $message, ValidationContext $context, string $value): Result
150
    {
151 73
        return (new Result())->addError($message, [
152 73
            'attribute' => $context->getAttribute(),
153
            'value' => $value,
154
        ]);
155
    }
156
}
157