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

EmailHandler::idnToAscii()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule\Email;
6
7
use Yiisoft\Validator\Result;
8
use Yiisoft\Validator\Rule\RuleHandlerInterface;
9
use Yiisoft\Validator\ValidationContext;
10
use Yiisoft\Validator\ValidatorInterface;
11
use function is_string;
12
use function strlen;
13
use Yiisoft\Validator\Exception\UnexpectedRuleException;
14
15
/**
16
 * Validates that the value is a valid email address.
17
 */
18
final class EmailHandler implements RuleHandlerInterface
19
{
20 84
    public function validate(mixed $value, object $rule, ValidatorInterface $validator, ?ValidationContext $context = null): Result
21
    {
22 84
        if (!$rule instanceof Email) {
23 1
            throw new UnexpectedRuleException(Email::class, $rule);
24
        }
25
26 83
        $originalValue = $value;
27 83
        $result = new Result();
28
29 83
        if (!is_string($value)) {
30 3
            $valid = false;
31 80
        } elseif (!preg_match(
32
            '/^(?P<name>(?:"?([^"]*)"?\s)?)(?:\s+)?((?P<open><?)((?P<local>.+)@(?P<domain>[^>]+))(?P<close>>?))$/i',
33
            $value,
34
            $matches
35
        )) {
36 6
            $valid = false;
37
        } else {
38
            /** @psalm-var array{name:string,local:string,open:string,domain:string,close:string} $matches */
39 74
            if ($rule->enableIDN) {
40 27
                $matches['local'] = $this->idnToAscii($matches['local']);
41 27
                $matches['domain'] = $this->idnToAscii($matches['domain']);
42 27
                $value = implode([
43 27
                    $matches['name'],
44 27
                    $matches['open'],
45 27
                    $matches['local'],
46
                    '@',
47 27
                    $matches['domain'],
48 27
                    $matches['close'],
49
                ]);
50
            }
51
52 74
            if (is_string($matches['local']) && strlen($matches['local']) > 64) {
53
                // The maximum total length of a user name or other local-part is 64 octets. RFC 5322 section 4.5.3.1.1
54
                // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1
55 1
                $valid = false;
56 73
            } elseif (is_string($matches['local']) && strlen($matches['local'] . '@' . $matches['domain']) > 254) {
57
                // There is a restriction in RFC 2821 on the length of an address in MAIL and RCPT commands
58
                // of 254 characters. Since addresses that do not fit in those fields are not normally useful, the
59
                // upper limit on address lengths should normally be considered to be 254.
60
                //
61
                // Dominic Sayers, RFC 3696 erratum 1690
62
                // http://www.rfc-editor.org/errata_search.php?eid=1690
63
                $valid = false;
64
            } else {
65 73
                $valid = preg_match($rule->pattern, $value) || ($rule->allowName && preg_match(
66 47
                    $rule->fullPattern,
67
                    $value
68
                ));
69 73
                if ($valid && $rule->checkDNS) {
70 5
                    $valid = checkdnsrr($matches['domain'] . '.', 'MX') || checkdnsrr($matches['domain'] . '.', 'A');
71
                }
72
            }
73
        }
74
75 83
        if ($rule->enableIDN && $valid === false) {
76 10
            $valid = (bool) preg_match($rule->idnEmailPattern, $originalValue);
77
        }
78
79 83
        if ($valid === false) {
80 45
            $result->addError($rule->message);
81
        }
82
83 83
        return $result;
84
    }
85
86 27
    private function idnToAscii($idn)
87
    {
88 27
        return idn_to_ascii($idn, 0, INTL_IDNA_VARIANT_UTS46);
89
    }
90
}
91