Guest User

Zend Email Validator

a guest
Sep 2nd, 2011
1,181
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 17.42 KB | None | 0 0
  1. <?php
  2. /**
  3.  * Zend Framework
  4.  *
  5.  * LICENSE
  6.  *
  7.  * This source file is subject to the new BSD license that is bundled
  8.  * with this package in the file LICENSE.txt.
  9.  * It is also available through the world-wide-web at this URL:
  10.  * http://framework.zend.com/license/new-bsd
  11.  * If you did not receive a copy of the license and are unable to
  12.  * obtain it through the world-wide-web, please send an email
  13.  * to [email protected] so we can send you a copy immediately.
  14.  *
  15.  * @category   Zend
  16.  * @package    Zend_Validate
  17.  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  18.  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  19.  * @version    $Id: EmailAddress.php 20358 2010-01-17 19:03:49Z thomas $
  20.  */
  21.  
  22. /**
  23.  * @see Zend_Validate_Abstract
  24.  */
  25. require_once 'Zend/Validate/Abstract.php';
  26.  
  27. /**
  28.  * @see Zend_Validate_Hostname
  29.  */
  30. require_once 'Zend/Validate/Hostname.php';
  31.  
  32. /**
  33.  * @category   Zend
  34.  * @package    Zend_Validate
  35.  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  36.  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  37.  */
  38. class Zend_Validate_EmailAddress extends Zend_Validate_Abstract
  39. {
  40.     const INVALID            = 'emailAddressInvalid';
  41.     const INVALID_FORMAT     = 'emailAddressInvalidFormat';
  42.     const INVALID_HOSTNAME   = 'emailAddressInvalidHostname';
  43.     const INVALID_MX_RECORD  = 'emailAddressInvalidMxRecord';
  44.     const INVALID_SEGMENT    = 'emailAddressInvalidSegment';
  45.     const DOT_ATOM           = 'emailAddressDotAtom';
  46.     const QUOTED_STRING      = 'emailAddressQuotedString';
  47.     const INVALID_LOCAL_PART = 'emailAddressInvalidLocalPart';
  48.     const LENGTH_EXCEEDED    = 'emailAddressLengthExceeded';
  49.  
  50.     /**
  51.      * @var array
  52.      */
  53.     protected $_messageTemplates = array(
  54.         self::INVALID            => "Invalid type given, value should be a string",
  55.         self::INVALID_FORMAT     => "'%value%' is no valid email address in the basic format local-part@hostname",
  56.         self::INVALID_HOSTNAME   => "'%hostname%' is no valid hostname for email address '%value%'",
  57.         self::INVALID_MX_RECORD  => "'%hostname%' does not appear to have a valid MX record for the email address '%value%'",
  58.         self::INVALID_SEGMENT    => "'%hostname%' is not in a routable network segment. The email address '%value%' should not be resolved from public network.",
  59.         self::DOT_ATOM           => "'%localPart%' can not be matched against dot-atom format",
  60.         self::QUOTED_STRING      => "'%localPart%' can not be matched against quoted-string format",
  61.         self::INVALID_LOCAL_PART => "'%localPart%' is no valid local part for email address '%value%'",
  62.         self::LENGTH_EXCEEDED    => "'%value%' exceeds the allowed length",
  63.     );
  64.  
  65.     /**
  66.      * @see http://en.wikipedia.org/wiki/IPv4
  67.      * @var array
  68.      */
  69.     protected $_invalidIp = array(
  70.         '0'   => '0.0.0.0/8',
  71.         '10'  => '10.0.0.0/8',
  72.         '127' => '127.0.0.0/8',
  73.         '128' => '128.0.0.0/16',
  74.         '169' => '169.254.0.0/16',
  75.         '172' => '172.16.0.0/12',
  76.         '191' => '191.255.0.0/16',
  77.         '192' => array(
  78.             '192.0.0.0/24',
  79.             '192.0.2.0/24',
  80.             '192.88.99.0/24',
  81.             '192.168.0.0/16'
  82.         ),
  83.         '198' => '198.18.0.0/15',
  84.         '223' => '223.255.255.0/24',
  85.         '224' => '224.0.0.0/4',
  86.         '240' => '240.0.0.0/4'
  87.     );
  88.  
  89.     /**
  90.      * @var array
  91.      */
  92.     protected $_messageVariables = array(
  93.         'hostname'  => '_hostname',
  94.         'localPart' => '_localPart'
  95.     );
  96.  
  97.     /**
  98.      * @var string
  99.      */
  100.     protected $_hostname;
  101.  
  102.     /**
  103.      * @var string
  104.      */
  105.     protected $_localPart;
  106.  
  107.     /**
  108.      * Internal options array
  109.      */
  110.     protected $_options = array(
  111.         'mx'       => false,
  112.         'deep'     => false,
  113.         'domain'   => true,
  114.         'allow'    => Zend_Validate_Hostname::ALLOW_DNS,
  115.         'hostname' => null
  116.     );
  117.  
  118.     /**
  119.      * Instantiates hostname validator for local use
  120.      *
  121.      * The following option keys are supported:
  122.      * 'hostname' => A hostname validator, see Zend_Validate_Hostname
  123.      * 'allow'    => Options for the hostname validator, see Zend_Validate_Hostname::ALLOW_*
  124.      * 'mx'       => If MX check should be enabled, boolean
  125.      * 'deep'     => If a deep MX check should be done, boolean
  126.      *
  127.      * @param array|Zend_Config $options OPTIONAL
  128.      * @return void
  129.      */
  130.     public function __construct($options = array())
  131.     {
  132.         if ($options instanceof Zend_Config) {
  133.             $options = $options->toArray();
  134.         } else if (!is_array($options)) {
  135.             $options = func_get_args();
  136.             $temp['allow'] = array_shift($options);
  137.             if (!empty($options)) {
  138.                 $temp['mx'] = array_shift($options);
  139.             }
  140.  
  141.             if (!empty($options)) {
  142.                 $temp['hostname'] = array_shift($options);
  143.             }
  144.  
  145.             $options = $temp;
  146.         }
  147.  
  148.         $options += $this->_options;
  149.         $this->setOptions($options);
  150.     }
  151.  
  152.     /**
  153.      * Returns all set Options
  154.      *
  155.      * @return array
  156.      */
  157.     public function getOptions()
  158.     {
  159.         return $this->_options;
  160.     }
  161.  
  162.     /**
  163.      * Set options for the email validator
  164.      *
  165.      * @param array $options
  166.      * @return Zend_Validate_EmailAddress fluid interface
  167.      */
  168.     public function setOptions(array $options = array())
  169.     {
  170.         if (array_key_exists('messages', $options)) {
  171.             $this->setMessages($options['messages']);
  172.         }
  173.  
  174.         if (array_key_exists('hostname', $options)) {
  175.             if (array_key_exists('allow', $options)) {
  176.                 $this->setHostnameValidator($options['hostname'], $options['allow']);
  177.             } else {
  178.                 $this->setHostnameValidator($options['hostname']);
  179.             }
  180.         }
  181.  
  182.         if (array_key_exists('mx', $options)) {
  183.             $this->setValidateMx($options['mx']);
  184.         }
  185.  
  186.         if (array_key_exists('deep', $options)) {
  187.             $this->setDeepMxCheck($options['deep']);
  188.         }
  189.  
  190.         if (array_key_exists('domain', $options)) {
  191.             $this->setDomainCheck($options['domain']);
  192.         }
  193.  
  194.         return $this;
  195.     }
  196.  
  197.     /**
  198.      * Sets the validation failure message template for a particular key
  199.      * Adds the ability to set messages to the attached hostname validator
  200.      *
  201.      * @param  string $messageString
  202.      * @param  string $messageKey     OPTIONAL
  203.      * @return Zend_Validate_Abstract Provides a fluent interface
  204.      * @throws Zend_Validate_Exception
  205.      */
  206.     public function setMessage($messageString, $messageKey = null)
  207.     {
  208.         $messageKeys = $messageKey;
  209.         if ($messageKey === null) {
  210.             $keys = array_keys($this->_messageTemplates);
  211.             $messageKeys = current($keys);
  212.         }
  213.  
  214.         if (!isset($this->_messageTemplates[$messageKeys])) {
  215.             $this->_options['hostname']->setMessage($messageString, $messageKey);
  216.         }
  217.  
  218.         $this->_messageTemplates[$messageKeys] = $messageString;
  219.         return $this;
  220.     }
  221.  
  222.     /**
  223.      * Returns the set hostname validator
  224.      *
  225.      * @return Zend_Validate_Hostname
  226.      */
  227.     public function getHostnameValidator()
  228.     {
  229.         return $this->_options['hostname'];
  230.     }
  231.  
  232.     /**
  233.      * @param Zend_Validate_Hostname $hostnameValidator OPTIONAL
  234.      * @param int                    $allow             OPTIONAL
  235.      * @return void
  236.      */
  237.     public function setHostnameValidator(Zend_Validate_Hostname $hostnameValidator = null, $allow = Zend_Validate_Hostname::ALLOW_DNS)
  238.     {
  239.         if (!$hostnameValidator) {
  240.             $hostnameValidator = new Zend_Validate_Hostname($allow);
  241.         }
  242.  
  243.         $this->_options['hostname'] = $hostnameValidator;
  244.         $this->_options['allow']    = $allow;
  245.         return $this;
  246.     }
  247.  
  248.     /**
  249.      * Whether MX checking via getmxrr is supported or not
  250.      *
  251.      * This currently only works on UNIX systems
  252.      *
  253.      * @return boolean
  254.      */
  255.     public function validateMxSupported()
  256.     {
  257.         return function_exists('getmxrr');
  258.     }
  259.  
  260.     /**
  261.      * Returns the set validateMx option
  262.      *
  263.      * @return boolean
  264.      */
  265.     public function getValidateMx()
  266.     {
  267.         return $this->_options['mx'];
  268.     }
  269.  
  270.     /**
  271.      * Set whether we check for a valid MX record via DNS
  272.      *
  273.      * This only applies when DNS hostnames are validated
  274.      *
  275.      * @param boolean $mx Set allowed to true to validate for MX records, and false to not validate them
  276.      * @return Zend_Validate_EmailAddress Fluid Interface
  277.      */
  278.     public function setValidateMx($mx)
  279.     {
  280.         if ((bool) $mx && !$this->validateMxSupported()) {
  281.             require_once 'Zend/Validate/Exception.php';
  282.             throw new Zend_Validate_Exception('MX checking not available on this system');
  283.         }
  284.  
  285.         $this->_options['mx'] = (bool) $mx;
  286.         return $this;
  287.     }
  288.  
  289.     /**
  290.      * Returns the set deepMxCheck option
  291.      *
  292.      * @return boolean
  293.      */
  294.     public function getDeepMxCheck()
  295.     {
  296.         return $this->_options['deep'];
  297.     }
  298.  
  299.     /**
  300.      * Set whether we check MX record should be a deep validation
  301.      *
  302.      * @param boolean $deep Set deep to true to perform a deep validation process for MX records
  303.      * @return Zend_Validate_EmailAddress Fluid Interface
  304.      */
  305.     public function setDeepMxCheck($deep)
  306.     {
  307.         $this->_options['deep'] = (bool) $deep;
  308.         return $this;
  309.     }
  310.  
  311.     /**
  312.      * Returns the set domainCheck option
  313.      *
  314.      * @return unknown
  315.      */
  316.     public function getDomainCheck()
  317.     {
  318.         return $this->_options['domain'];
  319.     }
  320.  
  321.     /**
  322.      * Sets if the domain should also be checked
  323.      * or only the local part of the email address
  324.      *
  325.      * @param boolean $domain
  326.      * @return Zend_Validate_EmailAddress Fluid Interface
  327.      */
  328.     public function setDomainCheck($domain = true)
  329.     {
  330.         $this->_options['domain'] = (boolean) $domain;
  331.         return $this;
  332.     }
  333.  
  334.     /**
  335.      * Returns if the given host is reserved
  336.      *
  337.      * @param string $host
  338.      * @return boolean
  339.      */
  340.     private function _isReserved($host){
  341.         if (!preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $host)) {
  342.             $host = gethostbyname($host);
  343.         }
  344.  
  345.         $octet = explode('.',$host);
  346.         if ((int)$octet[0] >= 224) {
  347.             return true;
  348.         } else if (array_key_exists($octet[0], $this->_invalidIp)) {
  349.             foreach ((array)$this->_invalidIp[$octet[0]] as $subnetData) {
  350.                 // we skip the first loop as we already know that octet matches
  351.                 for ($i = 1; $i < 4; $i++) {
  352.                     if (strpos($subnetData, $octet[$i]) !== $i * 4) {
  353.                         break;
  354.                     }
  355.                 }
  356.  
  357.                 $host       = explode("/", $subnetData);
  358.                 $binaryHost = "";
  359.                 $tmp        = explode(".", $host[0]);
  360.                 for ($i = 0; $i < 4 ; $i++) {
  361.                     $binaryHost .= str_pad(decbin($tmp[$i]), 8, "0", STR_PAD_LEFT);
  362.                 }
  363.  
  364.                 $segmentData = array(
  365.                     'network'   => (int)$this->_binaryToIp(str_pad(substr($binaryHost, 0, $host[1]), 32, 0)),
  366.                     'broadcast' => (int)$this->_binaryToIp(str_pad(substr($binaryHost, 0, $host[1]), 32, 1))
  367.                 );
  368.  
  369.                 for ($j = $i; $j < 4; $j++) {
  370.                     if ((int)$octet[$j] < $segmentData['network'][$j] ||
  371.                         (int)$octet[$j] > $segmentData['broadcast'][$j]) {
  372.                         return false;
  373.                     }
  374.                 }
  375.             }
  376.  
  377.             return true;
  378.         } else {
  379.             return false;
  380.         }
  381.     }
  382.  
  383.     /**
  384.      * Converts a binary string to an IP address
  385.      *
  386.      * @param string $binary
  387.      * @return mixed
  388.      */
  389.     private function _toIp($binary)
  390.     {
  391.         $ip  = array();
  392.         $tmp = explode(".", chunk_split($binary, 8, "."));
  393.         for ($i = 0; $i < 4 ; $i++) {
  394.             $ip[$i] = bindec($tmp[$i]);
  395.         }
  396.  
  397.         return $ip;
  398.     }
  399.  
  400.     /**
  401.      * Internal method to validate the local part of the email address
  402.      *
  403.      * @return boolean
  404.      */
  405.     private function _validateLocalPart()
  406.     {
  407.         // First try to match the local part on the common dot-atom format
  408.         $result = false;
  409.  
  410.         // Dot-atom characters are: 1*atext *("." 1*atext)
  411.         // atext: ALPHA / DIGIT / and "!", "#", "$", "%", "&", "'", "*",
  412.         //        "+", "-", "/", "=", "?", "^", "_", "`", "{", "|", "}", "~"
  413.         $atext = 'a-zA-Z0-9\x21\x23\x24\x25\x26\x27\x2a\x2b\x2d\x2f\x3d\x3f\x5e\x5f\x60\x7b\x7c\x7d\x7e';
  414.         if (preg_match('/^[' . $atext . ']+(\x2e+[' . $atext . ']+)*$/', $this->_localPart)) {
  415.             $result = true;
  416.         } else {
  417.             // Try quoted string format
  418.  
  419.             // Quoted-string characters are: DQUOTE *([FWS] qtext/quoted-pair) [FWS] DQUOTE
  420.             // qtext: Non white space controls, and the rest of the US-ASCII characters not
  421.             //   including "\" or the quote character
  422.             $noWsCtl = '\x01-\x08\x0b\x0c\x0e-\x1f\x7f';
  423.             $qtext   = $noWsCtl . '\x21\x23-\x5b\x5d-\x7e';
  424.             $ws      = '\x20\x09';
  425.             if (preg_match('/^\x22([' . $ws . $qtext . '])*[$ws]?\x22$/', $this->_localPart)) {
  426.                 $result = true;
  427.             } else {
  428.                 $this->_error(self::DOT_ATOM);
  429.                 $this->_error(self::QUOTED_STRING);
  430.                 $this->_error(self::INVALID_LOCAL_PART);
  431.             }
  432.         }
  433.  
  434.         return $result;
  435.     }
  436.  
  437.     /**
  438.      * Internal method to validate the servers MX records
  439.      *
  440.      * @return boolean
  441.      */
  442.     private function _validateMXRecords()
  443.     {
  444.         $result = true;
  445.         $mxHosts = array();
  446.         getmxrr($this->_hostname, $mxHosts);
  447.         if ($this->_options['deep'] && function_exists('checkdnsrr')) {
  448.             $validAddress = false;
  449.             $reserved     = true;
  450.             foreach ($mxHosts as $hostname) {
  451.                 $res = $this->_isReserved($hostname);
  452.                 if (!$res) {
  453.                     $reserved = false;
  454.                 }
  455.  
  456.                 if (!$res
  457.                     && (checkdnsrr($hostname, "A")
  458.                     || checkdnsrr($hostname, "AAAA")
  459.                     || checkdnsrr($hostname, "A6"))) {
  460.                     $validAddress = true;
  461.                     break;
  462.                 }
  463.             }
  464.  
  465.             if (!$validAddress) {
  466.                 $result = false;
  467.                 if ($reserved) {
  468.                     $this->_error(self::INVALID_SEGMENT);
  469.                 } else {
  470.                     $this->_error(self::INVALID_MX_RECORD);
  471.                 }
  472.             }
  473.         }
  474.  
  475.         return $result;
  476.     }
  477.  
  478.     /**
  479.      * Internal method to validate the hostname part of the email address
  480.      *
  481.      * @return boolean
  482.      */
  483.     private function _validateHostnamePart()
  484.     {
  485.         $hostname = $this->_options['hostname']->setTranslator($this->getTranslator())
  486.                          ->isValid($this->_hostname);
  487.         if (!$hostname) {
  488.             $this->_error(self::INVALID_HOSTNAME);
  489.  
  490.             // Get messages and errors from hostnameValidator
  491.             foreach ($this->_options['hostname']->getMessages() as $code => $message) {
  492.                 $this->_messages[$code] = $message;
  493.             }
  494.  
  495.             foreach ($this->_options['hostname']->getErrors() as $error) {
  496.                 $this->_errors[] = $error;
  497.             }
  498.         } else if ($this->_options['mx']) {
  499.             // MX check on hostname
  500.             $hostname = $this->_validateMXRecords();
  501.         }
  502.  
  503.         return $hostname;
  504.     }
  505.  
  506.     /**
  507.      * Defined by Zend_Validate_Interface
  508.      *
  509.      * Returns true if and only if $value is a valid email address
  510.      * according to RFC2822
  511.      *
  512.      * @link   http://www.ietf.org/rfc/rfc2822.txt RFC2822
  513.      * @link   http://www.columbia.edu/kermit/ascii.html US-ASCII characters
  514.      * @param  string $value
  515.      * @return boolean
  516.      */
  517.     public function isValid($value)
  518.     {
  519.         if (!is_string($value)) {
  520.             $this->_error(self::INVALID);
  521.             return false;
  522.         }
  523.  
  524.         $matches = array();
  525.         $length  = true;
  526.         $this->_setValue($value);
  527.  
  528.         // Split email address up and disallow '..'
  529.         if ((strpos($value, '..') !== false) or
  530.             (!preg_match('/^(.+)@([^@]+)$/', $value, $matches))) {
  531.             $this->_error(self::INVALID_FORMAT);
  532.             return false;
  533.         }
  534.  
  535.         $this->_localPart = $matches[1];
  536.         $this->_hostname  = $matches[2];
  537.  
  538.         if ((strlen($this->_localPart) > 64) || (strlen($this->_hostname) > 255)) {
  539.             $length = false;
  540.             $this->_error(self::LENGTH_EXCEEDED);
  541.         }
  542.  
  543.         // Match hostname part
  544.         if ($this->_options['domain']) {
  545.             $hostname = $this->_validateHostnamePart();
  546.         }
  547.  
  548.         $local = $this->_validateLocalPart();
  549.  
  550.         // If both parts valid, return true
  551.         if ($local && $length) {
  552.             if (($this->_options['domain'] && $hostname) || !$this->_options['domain']) {
  553.                 return true;
  554.             }
  555.         }
  556.  
  557.         return false;
  558.     }
  559. }
Advertisement
Add Comment
Please, Sign In to add comment