PHP的FILTER_VALIDATE_EMAIL无法正确工作

21

我使用的是 PHP 5.3.10 版本。以下是代码:

<?php
$email = "test@example.c";
if (filter_var($email, FILTER_VALIDATE_EMAIL))
        echo "Email: ".$email." correct";
else
        echo "email not correct";
?>

它的返回值为:"邮件地址:test@example.c" 正确。

我认为只有一个字符的顶级域名是不正确的(根据此列表,我不知道有一个字符长度的TLD:http://data.iana.org/TLD/tlds-alpha-by-domain.txt)。

那么,FILTER_VALIDATE_EMAIL 过滤器是否工作正常?


2
@符号前的第一部分长度不能超过64个字符... 在我的情况下,这导致了几个小时的调试,但 FILTER_VALIDATE_EMAIL 是正确的... - Lajos Veres
5个回答

61
Validating e-mail addresses is kinda complicated. Take a look at this list:
有效的电子邮件地址
1. niceandsimple@example.com 2. very.common@example.com 3. a.little.lengthy.but.fine@dept.example.com 4. disposable.style.email.with+symbol@example.com 5. user@[IPv6:2001:db8:1ff::a0b:dbd0] 6. "much.more unusual"@example.com 7. "very.unusual.@.unusual.com"@example.com 8. "very.(),:;<>[]".VERY."very@\ "very".unusual"@strange.example.com 9. postbox@com (顶级域名是有效的主机名) 10. admin@mailserver1 (没有TLD的本地域名) 11. !#$%&'*+-/=?^_`{}|~@example.org 12. "()<>[]:,;@\"!#$%&'*+-/=?^_`{}| ~.a"@example.org 13. " "@example.org (引号之间有空格) 14. üñîçøðé@example.com (本地部分中的Unicode字符)

无效的电子邮件地址

  1. Abc.example.com(必须使用 @ 符号分隔本地和域部分)
  2. A@b@c@example.com(一个 @ 只允许在引号外面出现一次)
  3. a"b(c)d,e:f;gi[j\k]l@example.com(此本地部分中的特殊字符都不允许在引号外面出现)
  4. just"not"right@example.com(带引号的字符串必须由点分隔,或者是组成本地部分的唯一元素)
  5. this is"not\allowed@example.com(空格、引号和反斜杠只有在被反斜杠转义并包含在引号内时才允许存在)
  6. this\ still"not\allowed@example.com(即使经过转义(在其前面加上反斜杠),空格、引号和反斜杠仍然必须包含在引号内)

来源 http://en.wikipedia.org/wiki/Email_address

几乎所有的电子邮件验证实现都存在“漏洞”,但 PHP 实现可以正常工作,因为它接受所有常见的电子邮件地址。

更新:

发现于http://www.php.net/manual/en/filter.filters.validate.php

关于没有在域名部分中包含.的“部分”地址,源代码(在ext/filter/logical_filters.c中)中的一条评论这样解释了此拒绝:

 * The regex below is based on a regex by Michael Rushton.
 * However, it is not identical.  I changed it to only consider routeable
 * addresses as valid.  Michael's regex considers a@b a valid address
 * which conflicts with section 2.3.5 of RFC 5321 which states that:
 *
 *   Only resolvable, fully-qualified domain names (FQDNs) are permitted
 *   when domain names are used in SMTP.  In other words, names that can
 *   be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
 *   in Section 5) are permitted, as are CNAME RRs whose targets can be
 *   resolved, in turn, to MX or address RRs.  Local nicknames or
 *   unqualified names MUST NOT be used.

这里有一个指向Michael Rushton的课程链接(链接已损坏,请参见下面的源代码),该课程支持RFC 5321/5322。

<?php
  /**
   * Squiloople Framework
   *
   * LICENSE: Feel free to use and redistribute this code.
   *
   * @author Michael Rushton <michael@squiloople.com>
   * @link http://squiloople.com/
   * @package Squiloople
   * @version 1.0
   * @copyright © 2012 Michael Rushton
   */
  /**
   * Email Address Validator
   *
   * Validate email addresses according to the relevant standards
   */
  final class EmailAddressValidator
  {
    // The RFC 5321 constant
    const RFC_5321 = 5321;
    // The RFC 5322 constant
    const RFC_5322 = 5322;
    /**
     * The email address
     *
     * @access private
     * @var string $_email_address
     */
    private $_email_address;
    /**
     * A quoted string local part is either allowed (true) or not (false)
     *
     * @access private
     * @var boolean $_quoted_string
     */
    private $_quoted_string = FALSE;
    /**
     * An obsolete local part is either allowed (true) or not (false)
     *
     * @access private
     * @var boolean $_obsolete
     */
    private $_obsolete = FALSE;
    /**
     * A basic domain name is either required (true) or not (false)
     *
     * @access private
     * @var boolean $_basic_domain_name
     */
    private $_basic_domain_name = TRUE;
    /**
     * A domain literal domain is either allowed (true) or not (false)
     *
     * @access private
     * @var boolean $_domain_literal
     */
    private $_domain_literal = FALSE;
   /**
     * Comments and folding white spaces are either allowed (true) or not (false)
     *
     * @access private
     * @var boolean $_cfws
     */
    private $_cfws = FALSE;
    /**
     * Set the email address and turn on the relevant standard if required
     *
     * @access public
     * @param string $email_address
     * @param null|integer $standard
     */
    public function __construct($email_address, $standard = NULL)
    {
      // Set the email address
      $this->_email_address = $email_address;
      // Set the relevant standard or throw an exception if an unknown is requested
      switch ($standard)
      {
        // Do nothing if no standard requested
        case NULL:
          break;
        // Otherwise if RFC 5321 requested
        case self::RFC_5321:
          $this->setStandard5321();
          break;
        // Otherwise if RFC 5322 requested
        case self::RFC_5322:
          $this->setStandard5322();
          break;
        // Otherwise throw an exception
        default:
          throw new Exception('Unknown RFC standard for email address validation.');
      }
    }
    /**
     * Call the constructor fluently
     *
     * @access public
     * @static
     * @param string $email_address
     * @param null|integer $standard
     * @return EmailAddressValidator
     */
    public static function setEmailAddress($email_address, $standard = NULL)
    {
      return new self($email_address, $standard);
    }
    /**
     * Validate the email address using a basic standard
     *
     * @access public
     * @return EmailAddressValidator
     */
    public function setStandardBasic()
    {
      // A quoted string local part is not allowed
      $this->_quoted_string = FALSE;
      // An obsolete local part is not allowed
      $this->_obsolete = FALSE;
      // A basic domain name is required
      $this->_basic_domain_name = TRUE;
      // A domain literal domain is not allowed
      $this->_domain_literal = FALSE;
      // Comments and folding white spaces are not allowed
      $this->_cfws = FALSE;
      // Return the EmailAddressValidator object
      return $this;
    }
    /**
     * Validate the email address using RFC 5321
     *
     * @access public
     * @return EmailAddressValidator
     */
    public function setStandard5321()
    {
      // A quoted string local part is allowed
      $this->_quoted_string = TRUE;
      // An obsolete local part is not allowed
      $this->_obsolete = FALSE;
      // Only a basic domain name is not required
      $this->_basic_domain_name = FALSE;
      // A domain literal domain is allowed
      $this->_domain_literal = TRUE;
      // Comments and folding white spaces are not allowed
      $this->_cfws = FALSE;
      // Return the EmailAddressValidator object
      return $this;
    }
    /**
     * Validate the email address using RFC 5322
     *
     * @access public
     * @return EmailAddressValidator
     */
    public function setStandard5322()
    {
      // A quoted string local part is disallowed
      $this->_quoted_string = FALSE;
      // An obsolete local part is allowed
      $this->_obsolete = TRUE;
      // Only a basic domain name is not required
      $this->_basic_domain_name = FALSE;
      // A domain literal domain is allowed
      $this->_domain_literal = TRUE;
      // Comments and folding white spaces are allowed
      $this->_cfws = TRUE;
      // Return the EmailAddressValidator object
      return $this;
    }
    /**
     * Either allow (true) or do not allow (false) a quoted string local part
     *
     * @access public
     * @param boolean $allow
     * @return EmailAddressValidator
     */
    public function setQuotedString($allow = TRUE)
    {
      // Either allow (true) or do not allow (false) a quoted string local part
      $this->_quoted_string = $allow;
      // Return the EmailAddressValidator object
      return $this;
    }
    /**
     * Either allow (true) or do not allow (false) an obsolete local part
     *
     * @access public
     * @param boolean $allow
     * @return EmailAddressValidator
     */
    public function setObsolete($allow = TRUE)
    {
      // Either allow (true) or do not allow (false) an obsolete local part
      $this->_obsolete = $allow;
      // Return the EmailAddressValidator object
      return $this;
    }
    /**
     * Either require (true) or do not require (false) a basic domain name
     *
     * @access public
     * @param boolean $allow
     * @return EmailAddressValidator
     */
    public function setBasicDomainName($allow = TRUE)
    {
      // Either require (true) or do not require (false) a basic domain name
      $this->_basic_domain_name = $allow;
      // Return the EmailAddressValidator object
      return $this;
    }
    /**
     * Either allow (true) or do not allow (false) a domain literal domain
     *
     * @access public
     * @param boolean $allow
     * @return EmailAddressValidator
     */
    public function setDomainLiteral($allow = TRUE)
    {
      // Either allow (true) or do not allow (false) a domain literal domain
      $this->_domain_literal = $allow;
      // Return the EmailAddressValidator object
      return $this;
    }
    /**
     * Either allow (true) or do not allow (false) comments and folding white spaces
     *
     * @access public
     * @param boolean $allow
     * @return EmailAddressValidator
     */
    public function setCFWS($allow = TRUE)
    {
      // Either allow (true) or do not allow (false) comments and folding white spaces
      $this->_cfws = $allow;
      // Return the EmailAddressValidator object
      return $this;
    }
    /**
     * Return the regular expression for a dot atom local part
     *
     * @access private
     * @return string
     */
    private function _getDotAtom()
    {
      return "([!#-'*+\/-9=?^-~-]+)(?>\.(?1))*";
    }
    /**
     * Return the regular expression for a quoted string local part
     *
     * @access private
     * @return string
     */
    private function _getQuotedString()
    {
      return '"(?>[ !#-\[\]-~]|\\\[ -~])*"';
    }
    /**
     * Return the regular expression for an obsolete local part
     *
     * @access private
     * @return string
     */
    private function _getObsolete()
    {
      return '([!#-\'*+\/-9=?^-~-]+|"(?>'
        . $this->_getFWS()
        . '(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*'
        . $this->_getFWS()
        . '")(?>'
        . $this->_getCFWS()
        . '\.'
        . $this->_getCFWS()
        . '(?1))*';
    }
    /**
     * Return the regular expression for a domain name domain
     *
     * @access private
     * @return string
     */
    private function _getDomainName()
    {
      // Return the basic domain name format if required
      if ($this->_basic_domain_name)
      {
        return '(?>' . $this->_getDomainNameLengthLimit()
          . '[a-z\d](?>[a-z\d-]*[a-z\d])?'
          . $this->_getCFWS()
          . '\.'
          . $this->_getCFWS()
          . '){1,126}[a-z]{2,6}';
      }
      // Otherwise return the full domain name format
      return $this->_getDomainNameLengthLimit()
        . '([a-z\d](?>[a-z\d-]*[a-z\d])?)(?>'
        . $this->_getCFWS()
        . '\.'
        . $this->_getDomainNameLengthLimit()
        . $this->_getCFWS()
        . '(?2)){0,126}';
    }
    /**
     * Return the regular expression for an IPv6 address
     *
     * @access private
     * @return string
     */
    private function _getIPv6()
    {
      return '([a-f\d]{1,4})(?>:(?3)){7}|(?!(?:.*[a-f\d][:\]]){8,})((?3)(?>:(?3)){0,6})?::(?4)?';
    }
    /**
     * Return the regular expression for an IPv4-mapped IPv6 address
     *
     * @access private
     * @return string
     */
    private function _getIPv4MappedIPv6()
    {
      return '(?3)(?>:(?3)){5}:|(?!(?:.*[a-f\d]:){6,})(?5)?::(?>((?3)(?>:(?3)){0,4}):)?';
    }
    /**
     * Return the regular expression for an IPv4 address
     *
     * @access private
     * @return string
     */
    private function _getIPv4()
    {
      return '(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?6)){3}';
    }
    /**
     * Return the regular expression for a domain literal domain
     *
     * @access private
     * @return string
     */
    private function _getDomainLiteral()
    {
      return '\[(?:(?>IPv6:(?>'
        . $this->_getIPv6()
        . '))|(?>(?>IPv6:(?>'
        . $this->_getIPv4MappedIPv6()
        . '))?'
        . $this->_getIPv4()
        . '))\]';
    }
    /**
     * Return either the regular expression for folding white spaces or its backreference
     *
     * @access private
     * @param boolean $define
     * @return string
     */
    private function _getFWS($define = FALSE)
    {
      // Return the backreference if $define is set to FALSE otherwise return the regular expression
      if ($this->_cfws)
      {
        return !$define ? '(?P>fws)' : '(?<fws>(?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)';
      }
    }
    /**
     * Return the regular expression for comments
     *
     * @access private
     * @return string
     */
    private function _getComments()
    {
      return '(?<comment>\((?>'
        . $this->_getFWS()
        . '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?P>comment)))*'
        . $this->_getFWS()
        . '\))';
    }
    /**
     * Return either the regular expression for comments and folding white spaces or its backreference
     *
     * @access private
     * @param boolean $define
     * @return string
     */
    private function _getCFWS($define = FALSE)
    {
      // Return the backreference if $define is set to FALSE
      if ($this->_cfws && !$define)
      {
        return '(?P>cfws)';
      }
      // Otherwise return the regular expression
      if ($this->_cfws)
      {
        return '(?<cfws>(?>(?>(?>'
          . $this->_getFWS(TRUE)
          . $this->_getComments()
          . ')+'
          . $this->_getFWS()
          . ')|'
          . $this->_getFWS()
          . ')?)';
      }
    }
    /**
     * Establish and return the valid format for the local part
     *
     * @access private
     * @return string
     */
    private function _getLocalPart()
    {
      // The local part may be obsolete if allowed
      if ($this->_obsolete)
      {
        return $this->_getObsolete();
      }
      // Otherwise the local part must be either a dot atom or a quoted string if the latter is allowed
      if ($this->_quoted_string)
      {
        return '(?>' . $this->_getDotAtom() . '|' . $this->_getQuotedString() . ')';
      }
      // Otherwise the local part must be a dot atom
      return $this->_getDotAtom();
    }
    /**
     * Establish and return the valid format for the domain
     *
     * @access private
     * @return string
     */
    private function _getDomain()
    {
      // The domain must be either a domain name or a domain literal if the latter is allowed
      if ($this->_domain_literal)
      {
        return '(?>' . $this->_getDomainName() . '|' . $this->_getDomainLiteral() . ')';
      }
      // Otherwise the domain must be a domain name
      return $this->_getDomainName();
    }
    /**
     * Return the email address length limit
     *
     * @access private
     * @return string
     */
    private function _getEmailAddressLengthLimit()
    {
      return '(?!(?>' . $this->_getCFWS() . '"?(?>\\\[ -~]|[^"])"?' . $this->_getCFWS() . '){255,})';
    }
    /**
     * Return the local part length limit
     *
     * @access private
     * @return string
     */
    private function _getLocalPartLengthLimit()
    {
      return '(?!(?>' . $this->_getCFWS() . '"?(?>\\\[ -~]|[^"])"?' . $this->_getCFWS() . '){65,}@)';
    }
    /**
     * Establish and return the domain name length limit
     *
     * @access private
     * @return string
     */
    private function _getDomainNameLengthLimit()
    {
      return '(?!' . $this->_getCFWS() . '[a-z\d-]{64,})';
    }
    /**
     * Check to see if the domain can be resolved to MX RRs
     *
     * @access private
     * @param array $domain
     * @return integer|boolean
     */
    private function _verifyDomain($domain)
    {
      // Return 0 if the domain cannot be resolved to MX RRs
      if (!checkdnsrr(end($domain), 'MX'))
      {
        return 0;
      }
      // Otherwise return true
      return TRUE;
    }
    /**
     * Perform the validation check on the email address's syntax and, if required, call _verifyDomain()
     *
     * @access public
     * @param boolean $verify
     * @return boolean|integer
     */
    public function isValid($verify = FALSE)
    {
      // Return false if the email address has an incorrect syntax
      if (!preg_match(
          '/^'
        . $this->_getEmailAddressLengthLimit()
        . $this->_getLocalPartLengthLimit()
        . $this->_getCFWS()
        . $this->_getLocalPart()
        . $this->_getCFWS()
        . '@'
        . $this->_getCFWS()
        . $this->_getDomain()
        . $this->_getCFWS(TRUE)
        . '$/isD'
        , $this->_email_address
      ))
      {
        return FALSE;
      }
      // Otherwise check to see if the domain can be resolved to MX RRs if required
      if ($verify)
      {
        return $this->_verifyDomain(explode('@', $this->_email_address));
      }
      // Otherwise return 1
      return 1;
    }
  }

1
你说:9. postbox@com(顶级域名是有效的主机名),但是对于这个地址的FILTER_VALIDATE_EMAIL过滤器返回false!那么,该过滤器只接受某些电子邮件地址(包括“joe@example.c”)作为有效地址,而不接受其他地址(无论如何,像“postbox@com”一样有效)吗?这是一种奇怪的行为! - Rosario Russo
2
@visserSander: filter_var('"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com',FILTER_VALIDATE_EMAIL) 被接受了,但 postbox@com 没有。我认为这是一个错误。 - Elias Van Ootegem
5
@aaron: 我完全不同意你说的所有内容:你认为 PHP 的实现是正确的,但官方 RFC 规范是错误的?我是说...真的吗?自从更新以来,我有点明白为什么 a@b 不被视为有效了,尽管我还是认为这个决定在某种程度上也受到了懒惰的驱使...在测试环境中运行 mail('root@localhost'); 并不显得太过分,对吧? - Elias Van Ootegem
1
是的,PHP支持新的更长的顶级域名。但我注意到在php7.1中,filter_var不将उपयोगकर्ता@उदाहरण.कॉम视为有效的电子邮件地址。 - Sander Visser
1
FILTER_VALIDATE_EMAIL不支持处理国际电子邮件地址。这种格式会导致返回false,并且我每天都会发送此类电子邮件。{local}@{domain}.co.ke - S. Yacko
显示剩余10条评论

2

FILTER_VALIDATE_EMAIL不支持PHP 5.2.14。


我注意到这一点是因为它在我的本地机器上运行正常,但当我将代码上传到托管服务器时却无法工作。 - CodeGodie

2

有一个用于验证电子邮件地址的PHP类可以在Google Code上找到:

http://code.google.com/p/php-email-address-validation

您可以像这样使用它:

include('EmailAddressValidator.php');
$validator = new EmailAddressValidator;
if ($validator->check_email_address('test@example.org')) { 
    // Email address is technically valid 
} else {
    // Email not valid
}

2
已被弃用...请使用 PHP 的 http://php.net/manual/en/filter.filters.validate.php,该函数涵盖了 RFC 822 中几乎所有的用例。 - MarcoZen

0

FILTER_VALIDATE_EMAIL 只能告诉您地址格式是否正确。

考虑以下示例:

// "not an email" is invalid so its false.
php > var_export(filter_var("not an email", FILTER_VALIDATE_EMAIL));
false
// "foo@a.com" looks like an email, so it passes even though its not real.
php > var_export(filter_var("foo@a.com", FILTER_VALIDATE_EMAIL));
'foo@a.com'
// "foo@gmail.com" passes, gmail is a valid email server,
//  but gmail require more than 3 letters for the address.
var_export(filter_var("foo@gmail.com", FILTER_VALIDATE_EMAIL));
'foo@gmail.com'

FILTER_VALIDATE_EMAIL 只能告诉你什么时候完全错误,而不能告诉你什么时候正确。

你需要使用像 Real Email 这样的服务,它可以进行深入的电子邮件地址验证。

// foo@gmail.com is invalid because gmail require more than 3 letters for the address.
var_export(file_get_contents("https://isitarealemail.com/api/email/validate?email=foo@gmail.com"));
'{"status":"invalid"}'

// foobar@gmail.com is valid
var_export(file_get_contents("https://isitarealemail.com/api/email/validate?email=foobar@gmail.com"));
'{"status":"valid"}'

查看更多,请参见https://docs.isitarealemail.com/how-to-validate-email-addresses-in-php


-2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接