File indexing completed on 2024-05-12 06:03:13
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Validate 0017 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0018 * @license http://framework.zend.com/license/new-bsd New BSD License 0019 * @version $Id$ 0020 */ 0021 0022 /** 0023 * @see Zend_Validate_Abstract 0024 */ 0025 // require_once 'Zend/Validate/Abstract.php'; 0026 0027 /** 0028 * @see Zend_Validate_Hostname 0029 */ 0030 // require_once 'Zend/Validate/Hostname.php'; 0031 0032 /** 0033 * @category Zend 0034 * @package Zend_Validate 0035 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0036 * @license http://framework.zend.com/license/new-bsd New BSD License 0037 */ 0038 class Zend_Validate_EmailAddress extends Zend_Validate_Abstract 0039 { 0040 const INVALID = 'emailAddressInvalid'; 0041 const INVALID_FORMAT = 'emailAddressInvalidFormat'; 0042 const INVALID_HOSTNAME = 'emailAddressInvalidHostname'; 0043 const INVALID_MX_RECORD = 'emailAddressInvalidMxRecord'; 0044 const INVALID_SEGMENT = 'emailAddressInvalidSegment'; 0045 const DOT_ATOM = 'emailAddressDotAtom'; 0046 const QUOTED_STRING = 'emailAddressQuotedString'; 0047 const INVALID_LOCAL_PART = 'emailAddressInvalidLocalPart'; 0048 const LENGTH_EXCEEDED = 'emailAddressLengthExceeded'; 0049 0050 /** 0051 * @var array 0052 */ 0053 protected $_messageTemplates = array( 0054 self::INVALID => "Invalid type given. String expected", 0055 self::INVALID_FORMAT => "'%value%' is not a valid email address in the basic format local-part@hostname", 0056 self::INVALID_HOSTNAME => "'%hostname%' is not a valid hostname for email address '%value%'", 0057 self::INVALID_MX_RECORD => "'%hostname%' does not appear to have a valid MX record for the email address '%value%'", 0058 self::INVALID_SEGMENT => "'%hostname%' is not in a routable network segment. The email address '%value%' should not be resolved from public network", 0059 self::DOT_ATOM => "'%localPart%' can not be matched against dot-atom format", 0060 self::QUOTED_STRING => "'%localPart%' can not be matched against quoted-string format", 0061 self::INVALID_LOCAL_PART => "'%localPart%' is not a valid local part for email address '%value%'", 0062 self::LENGTH_EXCEEDED => "'%value%' exceeds the allowed length", 0063 ); 0064 0065 /** 0066 * As of RFC5753 (JAN 2010), the following blocks are no longer reserved: 0067 * - 128.0.0.0/16 0068 * - 191.255.0.0/16 0069 * - 223.255.255.0/24 0070 * @see http://tools.ietf.org/html/rfc5735#page-6 0071 * 0072 * As of RFC6598 (APR 2012), the following blocks are now reserved: 0073 * - 100.64.0.0/10 0074 * @see http://tools.ietf.org/html/rfc6598#section-7 0075 * 0076 * @see http://en.wikipedia.org/wiki/IPv4 0077 * @var array 0078 */ 0079 protected $_invalidIp = array( 0080 '0' => '0.0.0.0/8', 0081 '10' => '10.0.0.0/8', 0082 '100' => '100.64.0.0/10', 0083 '127' => '127.0.0.0/8', 0084 '169' => '169.254.0.0/16', 0085 '172' => '172.16.0.0/12', 0086 '192' => array( 0087 '192.0.0.0/24', 0088 '192.0.2.0/24', 0089 '192.88.99.0/24', 0090 '192.168.0.0/16' 0091 ), 0092 '198' => '198.18.0.0/15', 0093 '224' => '224.0.0.0/4', 0094 '240' => '240.0.0.0/4' 0095 ); 0096 0097 /** 0098 * @var array 0099 */ 0100 protected $_messageVariables = array( 0101 'hostname' => '_hostname', 0102 'localPart' => '_localPart' 0103 ); 0104 0105 /** 0106 * @var string 0107 */ 0108 protected $_hostname; 0109 0110 /** 0111 * @var string 0112 */ 0113 protected $_localPart; 0114 0115 /** 0116 * Internal options array 0117 */ 0118 protected $_options = array( 0119 'mx' => false, 0120 'deep' => false, 0121 'domain' => true, 0122 'allow' => Zend_Validate_Hostname::ALLOW_DNS, 0123 'hostname' => null 0124 ); 0125 0126 /** 0127 * Instantiates hostname validator for local use 0128 * 0129 * The following option keys are supported: 0130 * 'hostname' => A hostname validator, see Zend_Validate_Hostname 0131 * 'allow' => Options for the hostname validator, see Zend_Validate_Hostname::ALLOW_* 0132 * 'mx' => If MX check should be enabled, boolean 0133 * 'deep' => If a deep MX check should be done, boolean 0134 * 0135 * @param array|string|Zend_Config $options OPTIONAL 0136 */ 0137 public function __construct($options = array()) 0138 { 0139 if ($options instanceof Zend_Config) { 0140 $options = $options->toArray(); 0141 } else if (!is_array($options)) { 0142 $options = func_get_args(); 0143 $temp['allow'] = array_shift($options); 0144 if (!empty($options)) { 0145 $temp['mx'] = array_shift($options); 0146 } 0147 0148 if (!empty($options)) { 0149 $temp['hostname'] = array_shift($options); 0150 } 0151 0152 $options = $temp; 0153 } 0154 0155 $options += $this->_options; 0156 $this->setOptions($options); 0157 } 0158 0159 /** 0160 * Returns all set Options 0161 * 0162 * @return array 0163 */ 0164 public function getOptions() 0165 { 0166 return $this->_options; 0167 } 0168 0169 /** 0170 * Set options for the email validator 0171 * 0172 * @param array $options 0173 * @return Zend_Validate_EmailAddress Provides a fluent inteface 0174 */ 0175 public function setOptions(array $options = array()) 0176 { 0177 if (array_key_exists('messages', $options)) { 0178 $this->setMessages($options['messages']); 0179 } 0180 0181 if (array_key_exists('hostname', $options)) { 0182 if (array_key_exists('allow', $options)) { 0183 $this->setHostnameValidator($options['hostname'], $options['allow']); 0184 } else { 0185 $this->setHostnameValidator($options['hostname']); 0186 } 0187 } elseif ($this->_options['hostname'] == null) { 0188 $this->setHostnameValidator(); 0189 } 0190 0191 if (array_key_exists('mx', $options)) { 0192 $this->setValidateMx($options['mx']); 0193 } 0194 0195 if (array_key_exists('deep', $options)) { 0196 $this->setDeepMxCheck($options['deep']); 0197 } 0198 0199 if (array_key_exists('domain', $options)) { 0200 $this->setDomainCheck($options['domain']); 0201 } 0202 0203 return $this; 0204 } 0205 0206 /** 0207 * Sets the validation failure message template for a particular key 0208 * Adds the ability to set messages to the attached hostname validator 0209 * 0210 * @param string $messageString 0211 * @param string $messageKey OPTIONAL 0212 * @return Zend_Validate_Abstract Provides a fluent interface 0213 * @throws Zend_Validate_Exception 0214 */ 0215 public function setMessage($messageString, $messageKey = null) 0216 { 0217 if ($messageKey === null) { 0218 $this->_options['hostname']->setMessage($messageString); 0219 parent::setMessage($messageString); 0220 return $this; 0221 } 0222 0223 if (!isset($this->_messageTemplates[$messageKey])) { 0224 $this->_options['hostname']->setMessage($messageString, $messageKey); 0225 } 0226 0227 $this->_messageTemplates[$messageKey] = $messageString; 0228 return $this; 0229 } 0230 0231 /** 0232 * Returns the set hostname validator 0233 * 0234 * @return Zend_Validate_Hostname 0235 */ 0236 public function getHostnameValidator() 0237 { 0238 return $this->_options['hostname']; 0239 } 0240 0241 /** 0242 * @param Zend_Validate_Hostname $hostnameValidator OPTIONAL 0243 * @param int $allow OPTIONAL 0244 * @return $this 0245 */ 0246 public function setHostnameValidator(Zend_Validate_Hostname $hostnameValidator = null, $allow = Zend_Validate_Hostname::ALLOW_DNS) 0247 { 0248 if (!$hostnameValidator) { 0249 $hostnameValidator = new Zend_Validate_Hostname($allow); 0250 } 0251 0252 $this->_options['hostname'] = $hostnameValidator; 0253 $this->_options['allow'] = $allow; 0254 return $this; 0255 } 0256 0257 /** 0258 * Whether MX checking via getmxrr is supported or not 0259 * 0260 * This currently only works on UNIX systems 0261 * 0262 * @return boolean 0263 */ 0264 public function validateMxSupported() 0265 { 0266 return function_exists('getmxrr'); 0267 } 0268 0269 /** 0270 * Returns the set validateMx option 0271 * 0272 * @return boolean 0273 */ 0274 public function getValidateMx() 0275 { 0276 return $this->_options['mx']; 0277 } 0278 0279 /** 0280 * Set whether we check for a valid MX record via DNS 0281 * 0282 * This only applies when DNS hostnames are validated 0283 * 0284 * @param boolean $mx Set allowed to true to validate for MX records, and false to not validate them 0285 * @throws Zend_Validate_Exception 0286 * @return Zend_Validate_EmailAddress Provides a fluent inteface 0287 */ 0288 public function setValidateMx($mx) 0289 { 0290 if ((bool) $mx && !$this->validateMxSupported()) { 0291 // require_once 'Zend/Validate/Exception.php'; 0292 throw new Zend_Validate_Exception('MX checking not available on this system'); 0293 } 0294 0295 $this->_options['mx'] = (bool) $mx; 0296 return $this; 0297 } 0298 0299 /** 0300 * Returns the set deepMxCheck option 0301 * 0302 * @return boolean 0303 */ 0304 public function getDeepMxCheck() 0305 { 0306 return $this->_options['deep']; 0307 } 0308 0309 /** 0310 * Set whether we check MX record should be a deep validation 0311 * 0312 * @param boolean $deep Set deep to true to perform a deep validation process for MX records 0313 * @return Zend_Validate_EmailAddress Provides a fluent inteface 0314 */ 0315 public function setDeepMxCheck($deep) 0316 { 0317 $this->_options['deep'] = (bool) $deep; 0318 return $this; 0319 } 0320 0321 /** 0322 * Returns the set domainCheck option 0323 * 0324 * @return unknown 0325 */ 0326 public function getDomainCheck() 0327 { 0328 return $this->_options['domain']; 0329 } 0330 0331 /** 0332 * Sets if the domain should also be checked 0333 * or only the local part of the email address 0334 * 0335 * @param boolean $domain 0336 * @return Zend_Validate_EmailAddress Provides a fluent inteface 0337 */ 0338 public function setDomainCheck($domain = true) 0339 { 0340 $this->_options['domain'] = (boolean) $domain; 0341 return $this; 0342 } 0343 0344 /** 0345 * Returns if the given host is reserved 0346 * 0347 * @param string $host 0348 * @return boolean 0349 */ 0350 private function _isReserved($host){ 0351 if (!preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $host)) { 0352 $host = gethostbyname($host); 0353 } 0354 0355 $octet = explode('.',$host); 0356 if ((int)$octet[0] >= 224) { 0357 return true; 0358 } else if (array_key_exists($octet[0], $this->_invalidIp)) { 0359 foreach ((array)$this->_invalidIp[$octet[0]] as $subnetData) { 0360 // we skip the first loop as we already know that octet matches 0361 for ($i = 1; $i < 4; $i++) { 0362 if (strpos($subnetData, $octet[$i]) !== $i * 4) { 0363 break; 0364 } 0365 } 0366 0367 $host = explode("/", $subnetData); 0368 $binaryHost = ""; 0369 $tmp = explode(".", $host[0]); 0370 for ($i = 0; $i < 4 ; $i++) { 0371 $binaryHost .= str_pad(decbin($tmp[$i]), 8, "0", STR_PAD_LEFT); 0372 } 0373 0374 $segmentData = array( 0375 'network' => (int)$this->_toIp(str_pad(substr($binaryHost, 0, $host[1]), 32, 0)), 0376 'broadcast' => (int)$this->_toIp(str_pad(substr($binaryHost, 0, $host[1]), 32, 1)) 0377 ); 0378 0379 for ($j = $i; $j < 4; $j++) { 0380 if ((int)$octet[$j] < $segmentData['network'][$j] || 0381 (int)$octet[$j] > $segmentData['broadcast'][$j]) { 0382 return false; 0383 } 0384 } 0385 } 0386 0387 return true; 0388 } else { 0389 return false; 0390 } 0391 } 0392 0393 /** 0394 * Converts a binary string to an IP address 0395 * 0396 * @param string $binary 0397 * @return mixed 0398 */ 0399 private function _toIp($binary) 0400 { 0401 $ip = array(); 0402 $tmp = explode(".", chunk_split($binary, 8, ".")); 0403 for ($i = 0; $i < 4 ; $i++) { 0404 $ip[$i] = bindec($tmp[$i]); 0405 } 0406 0407 return $ip; 0408 } 0409 0410 /** 0411 * Internal method to validate the local part of the email address 0412 * 0413 * @return boolean 0414 */ 0415 private function _validateLocalPart() 0416 { 0417 // First try to match the local part on the common dot-atom format 0418 $result = false; 0419 0420 // Dot-atom characters are: 1*atext *("." 1*atext) 0421 // atext: ALPHA / DIGIT / and "!", "#", "$", "%", "&", "'", "*", 0422 // "+", "-", "/", "=", "?", "^", "_", "`", "{", "|", "}", "~" 0423 $atext = 'a-zA-Z0-9\x21\x23\x24\x25\x26\x27\x2a\x2b\x2d\x2f\x3d\x3f\x5e\x5f\x60\x7b\x7c\x7d\x7e'; 0424 if (preg_match('/^[' . $atext . ']+(\x2e+[' . $atext . ']+)*$/', $this->_localPart)) { 0425 $result = true; 0426 } else { 0427 // Try quoted string format (RFC 5321 Chapter 4.1.2) 0428 0429 // Quoted-string characters are: DQUOTE *(qtext/quoted-pair) DQUOTE 0430 $qtext = '\x20-\x21\x23-\x5b\x5d-\x7e'; // %d32-33 / %d35-91 / %d93-126 0431 $quotedPair = '\x20-\x7e'; // %d92 %d32-126 0432 if (preg_match('/^"(['. $qtext .']|\x5c[' . $quotedPair . '])*"$/', $this->localPart)) { 0433 $result = true; 0434 } else { 0435 $this->_error(self::DOT_ATOM); 0436 $this->_error(self::QUOTED_STRING); 0437 $this->_error(self::INVALID_LOCAL_PART); 0438 } 0439 } 0440 0441 return $result; 0442 } 0443 0444 /** 0445 * Internal method to validate the servers MX records 0446 * 0447 * @return boolean 0448 */ 0449 private function _validateMXRecords() 0450 { 0451 $mxHosts = array(); 0452 $hostname = $this->_hostname; 0453 0454 //decode IDN domain name if possible 0455 if (function_exists('idn_to_ascii')) { 0456 $hostname = idn_to_ascii($this->_hostname); 0457 } 0458 0459 $result = getmxrr($hostname, $mxHosts); 0460 if (!$result) { 0461 $this->_error(self::INVALID_MX_RECORD); 0462 } else if ($this->_options['deep'] && function_exists('checkdnsrr')) { 0463 $validAddress = false; 0464 $reserved = true; 0465 foreach ($mxHosts as $hostname) { 0466 $res = $this->_isReserved($hostname); 0467 if (!$res) { 0468 $reserved = false; 0469 } 0470 0471 if (!$res 0472 && (checkdnsrr($hostname, "A") 0473 || checkdnsrr($hostname, "AAAA") 0474 || checkdnsrr($hostname, "A6"))) { 0475 $validAddress = true; 0476 break; 0477 } 0478 } 0479 0480 if (!$validAddress) { 0481 $result = false; 0482 if ($reserved) { 0483 $this->_error(self::INVALID_SEGMENT); 0484 } else { 0485 $this->_error(self::INVALID_MX_RECORD); 0486 } 0487 } 0488 } 0489 0490 return $result; 0491 } 0492 0493 /** 0494 * Internal method to validate the hostname part of the email address 0495 * 0496 * @return boolean 0497 */ 0498 private function _validateHostnamePart() 0499 { 0500 $hostname = $this->_options['hostname']->setTranslator($this->getTranslator()) 0501 ->isValid($this->_hostname); 0502 if (!$hostname) { 0503 $this->_error(self::INVALID_HOSTNAME); 0504 0505 // Get messages and errors from hostnameValidator 0506 foreach ($this->_options['hostname']->getMessages() as $code => $message) { 0507 $this->_messages[$code] = $message; 0508 } 0509 0510 foreach ($this->_options['hostname']->getErrors() as $error) { 0511 $this->_errors[] = $error; 0512 } 0513 } else if ($this->_options['mx']) { 0514 // MX check on hostname 0515 $hostname = $this->_validateMXRecords(); 0516 } 0517 0518 return $hostname; 0519 } 0520 0521 /** 0522 * Defined by Zend_Validate_Interface 0523 * 0524 * Returns true if and only if $value is a valid email address 0525 * according to RFC2822 0526 * 0527 * @link http://www.ietf.org/rfc/rfc2822.txt RFC2822 0528 * @link http://www.columbia.edu/kermit/ascii.html US-ASCII characters 0529 * @param string $value 0530 * @return boolean 0531 */ 0532 public function isValid($value) 0533 { 0534 if (!is_string($value)) { 0535 $this->_error(self::INVALID); 0536 return false; 0537 } 0538 0539 $matches = array(); 0540 $length = true; 0541 $this->_setValue($value); 0542 0543 // Split email address up and disallow '..' 0544 if ((strpos($value, '..') !== false) or 0545 (!preg_match('/^(.+)@([^@]+)$/', $value, $matches))) { 0546 $this->_error(self::INVALID_FORMAT); 0547 return false; 0548 } 0549 0550 $this->_localPart = $matches[1]; 0551 $this->_hostname = $matches[2]; 0552 0553 if ((strlen($this->_localPart) > 64) || (strlen($this->_hostname) > 255)) { 0554 $length = false; 0555 $this->_error(self::LENGTH_EXCEEDED); 0556 } 0557 0558 // Match hostname part 0559 if ($this->_options['domain']) { 0560 $hostname = $this->_validateHostnamePart(); 0561 } 0562 0563 $local = $this->_validateLocalPart(); 0564 0565 // If both parts valid, return true 0566 if ($local && $length) { 0567 if (($this->_options['domain'] && $hostname) || !$this->_options['domain']) { 0568 return true; 0569 } 0570 } 0571 0572 return false; 0573 } 0574 }