Passed
Pull Request — master (#222)
by Alexander
05:06 queued 02:29
created

EmailValidator   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 67
Duplicated Lines 0 %

Test Coverage

Coverage 96.77%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 36
c 1
b 0
f 0
dl 0
loc 67
ccs 30
cts 31
cp 0.9677
rs 10
wmc 17

2 Methods

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