Passed
Pull Request — master (#335)
by Sergei
02:50
created

EmailHandler   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 79
Duplicated Lines 0 %

Test Coverage

Coverage 94.59%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 19
eloc 41
c 1
b 0
f 0
dl 0
loc 79
ccs 35
cts 37
cp 0.9459
rs 10

3 Methods

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