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