| 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\Formatter; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 9 |  |  | use Yiisoft\Validator\FormatterInterface; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 10 |  |  | use Yiisoft\Validator\Result; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 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 |  |  |     private FormatterInterface $formatter; | 
            
                                                                        
                            
            
                                    
            
            
                | 22 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 23 | 84 |  |     public function __construct(?FormatterInterface $formatter = null) | 
            
                                                                        
                            
            
                                    
            
            
                | 24 |  |  |     { | 
            
                                                                        
                            
            
                                    
            
            
                | 25 | 84 |  |         $this->formatter = $formatter ?? new Formatter(); | 
            
                                                                        
                            
            
                                    
            
            
                | 26 |  |  |     } | 
            
                                                                        
                            
            
                                    
            
            
                | 27 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 28 | 84 |  |     public function validate(mixed $value, object $rule, ?ValidationContext $context = null): Result | 
            
                                                                        
                            
            
                                    
            
            
                | 29 |  |  |     { | 
            
                                                                        
                            
            
                                    
            
            
                | 30 | 84 |  |         if (!$rule instanceof Email) { | 
            
                                                                        
                            
            
                                    
            
            
                | 31 | 1 |  |             throw new UnexpectedRuleException(Email::class, $rule); | 
            
                                                                        
                            
            
                                    
            
            
                | 32 |  |  |         } | 
            
                                                                        
                            
            
                                    
            
            
                | 33 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 34 | 83 |  |         $originalValue = $value; | 
            
                                                                        
                            
            
                                    
            
            
                | 35 | 83 |  |         $result = new Result(); | 
            
                                                                        
                            
            
                                    
            
            
                | 36 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 37 | 83 |  |         if (!is_string($value)) { | 
            
                                                                        
                            
            
                                    
            
            
                | 38 | 3 |  |             $valid = false; | 
            
                                                                        
                            
            
                                    
            
            
                | 39 | 80 |  |         } elseif (!preg_match( | 
            
                                                                        
                            
            
                                    
            
            
                | 40 |  |  |             '/^(?P<name>(?:"?([^"]*)"?\s)?)(?:\s+)?((?P<open><?)((?P<local>.+)@(?P<domain>[^>]+))(?P<close>>?))$/i', | 
            
                                                                        
                            
            
                                    
            
            
                | 41 |  |  |             $value, | 
            
                                                                        
                            
            
                                    
            
            
                | 42 |  |  |             $matches | 
            
                                                                        
                            
            
                                    
            
            
                | 43 |  |  |         )) { | 
            
                                                                        
                            
            
                                    
            
            
                | 44 | 6 |  |             $valid = false; | 
            
                                                                        
                            
            
                                    
            
            
                | 45 |  |  |         } else { | 
            
                                                                        
                            
            
                                    
            
            
                | 46 |  |  |             /** @psalm-var array{name:string,local:string,open:string,domain:string,close:string} $matches */ | 
            
                                                                        
                            
            
                                    
            
            
                | 47 | 74 |  |             if ($rule->isEnableIDN()) { | 
            
                                                                        
                            
            
                                    
            
            
                | 48 | 27 |  |                 $matches['local'] = $this->idnToAscii($matches['local']); | 
            
                                                                        
                            
            
                                    
            
            
                | 49 | 27 |  |                 $matches['domain'] = $this->idnToAscii($matches['domain']); | 
            
                                                                        
                            
            
                                    
            
            
                | 50 | 27 |  |                 $value = implode([ | 
            
                                                                        
                            
            
                                    
            
            
                | 51 | 27 |  |                     $matches['name'], | 
            
                                                                        
                            
            
                                    
            
            
                | 52 | 27 |  |                     $matches['open'], | 
            
                                                                        
                            
            
                                    
            
            
                | 53 | 27 |  |                     $matches['local'], | 
            
                                                                        
                            
            
                                    
            
            
                | 54 |  |  |                     '@', | 
            
                                                                        
                            
            
                                    
            
            
                | 55 | 27 |  |                     $matches['domain'], | 
            
                                                                        
                            
            
                                    
            
            
                | 56 | 27 |  |                     $matches['close'], | 
            
                                                                        
                            
            
                                    
            
            
                | 57 |  |  |                 ]); | 
            
                                                                        
                            
            
                                    
            
            
                | 58 |  |  |             } | 
            
                                                                        
                            
            
                                    
            
            
                | 59 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 60 | 74 |  |             if (is_string($matches['local']) && strlen($matches['local']) > 64) { | 
            
                                                                        
                            
            
                                    
            
            
                | 61 |  |  |                 // The maximum total length of a user name or other local-part is 64 octets. RFC 5322 section 4.5.3.1.1 | 
            
                                                                        
                            
            
                                    
            
            
                | 62 |  |  |                 // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1 | 
            
                                                                        
                            
            
                                    
            
            
                | 63 | 1 |  |                 $valid = false; | 
            
                                                                        
                            
            
                                    
            
            
                | 64 | 73 |  |             } elseif (is_string($matches['local']) && strlen($matches['local'] . '@' . $matches['domain']) > 254) { | 
            
                                                                        
                            
            
                                    
            
            
                | 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 |  |  |                 // http://www.rfc-editor.org/errata_search.php?eid=1690 | 
            
                                                                        
                            
            
                                    
            
            
                | 71 |  |  |                 $valid = false; | 
            
                                                                        
                            
            
                                    
            
            
                | 72 |  |  |             } else { | 
            
                                                                        
                            
            
                                    
            
            
                | 73 | 73 |  |                 $valid = preg_match($rule->getPattern(), $value) || ($rule->isAllowName() && preg_match( | 
            
                                                                        
                            
            
                                    
            
            
                | 74 | 47 |  |                     $rule->getFullPattern(), | 
            
                                                                        
                            
            
                                    
            
            
                | 75 |  |  |                     $value | 
            
                                                                        
                            
            
                                    
            
            
                | 76 |  |  |                 )); | 
            
                                                                        
                            
            
                                    
            
            
                | 77 | 73 |  |                 if ($valid && $rule->isCheckDNS()) { | 
            
                                                                        
                            
            
                                    
            
            
                | 78 | 5 |  |                     $valid = checkdnsrr($matches['domain'] . '.') || checkdnsrr($matches['domain'] . '.', 'A'); | 
            
                                                                        
                            
            
                                    
            
            
                | 79 |  |  |                 } | 
            
                                                                        
                            
            
                                    
            
            
                | 80 |  |  |             } | 
            
                                                                        
                            
            
                                    
            
            
                | 81 |  |  |         } | 
            
                                                                        
                            
            
                                    
            
            
                | 82 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 83 | 83 |  |         if ($valid === false && $rule->isEnableIDN()) { | 
            
                                                                        
                            
            
                                    
            
            
                | 84 | 10 |  |             $valid = (bool) preg_match($rule->getIdnEmailPattern(), $originalValue); | 
            
                                                                        
                            
            
                                    
            
            
                | 85 |  |  |         } | 
            
                                                                        
                            
            
                                    
            
            
                | 86 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 87 | 83 |  |         if ($valid === false) { | 
            
                                                                        
                            
            
                                    
            
            
                | 88 | 45 |  |             $formattedMessage = $this->formatter->format( | 
            
                                                                        
                            
            
                                    
            
            
                | 89 | 45 |  |                 $rule->getMessage(), | 
            
                                                                        
                            
            
                                    
            
            
                | 90 | 45 |  |                 ['attribute' => $context?->getAttribute(), 'value' => $originalValue] | 
            
                                                                        
                            
            
                                    
            
            
                | 91 |  |  |             ); | 
            
                                                                        
                            
            
                                    
            
            
                | 92 | 45 |  |             $result->addError($formattedMessage); | 
            
                                                                        
                            
            
                                    
            
            
                | 93 |  |  |         } | 
            
                                                                        
                            
            
                                    
            
            
                | 94 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 95 | 83 |  |         return $result; | 
            
                                                                        
                            
            
                                    
            
            
                | 96 |  |  |     } | 
            
                                                                        
                            
            
                                    
            
            
                | 97 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 98 | 27 |  |     private function idnToAscii($idn): false|string | 
            
                                                                                                            
                            
            
                                    
            
            
                | 99 |  |  |     { | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 100 | 27 |  |         return idn_to_ascii($idn, 0, INTL_IDNA_VARIANT_UTS46); | 
            
                                                                        
                                                                
            
                                    
            
            
                | 101 |  |  |     } | 
            
                                                                        
                                                                
            
                                    
            
            
                | 102 |  |  | } | 
            
                                                                        
                                                                
            
                                    
            
            
                | 103 |  |  |  |