Invisimail stops encoding email addresses at the hyphen ("-") if the address domain contains one.

For example, if the email address is somebody@bsd-ri.net, Invisimail stops encoding the address after "bsd" and the mailto: link ends up as mailto:somebody@bsd

Tried changing encoding methods within the Invisimail formatter settings does not help.

CommentFileSizeAuthor
#3 invisimail-DRUPAL-6--1_domain_regex.patch939 bytesdboulet

Comments

oligog’s picture

try to change line 112 to:
$domain = '(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9\-][a-zA-Z0-9\-]*[a-zA-Z0-9\-])\.?)+';

I added the "\-" (without ") because there may be such a -character in a domain name...
& now it works!

The obfuscated e-mail still breaks the layout (i´ve to put it into the flow).

tiggr93’s picture

Confirm this fix does work, although I used a different regex for validating full domains (first & last character of subdomain must be alpha-numeric, hyphens only allowed in between, also validates TLD):

$domain = '([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}';

dboulet’s picture

Status: Active » Needs review
StatusFileSize
new939 bytes

Thanks for the code tiggr93, it also helped me with another issue I had where any word with the '@' symbol in it turned into a mailto link.

rkendall’s picture

Glad I spotted this issue before I noticed it as a problem on a site.

Wouldn't it be better to re-use a proven email/domain regex? While I'm glad of the suggested fix, I am left wondering what different issues it might throw up.

Some references:
http://code.iamcal.com/php/rfc822/
http://www.dominicsayers.com/isemail/
http://squiloople.com/2009/12/20/email-address-validation/
http://www.hm2k.com/posts/what-is-a-valid-email-address
http://fightingforalostcause.net/misc/2006/compare-email-regex.php

Crell’s picture

Status: Needs review » Fixed

Well, "real" email address validation takes several pages of incomprehensible regex. I'm not doing that. :-) For one, it's horribly difficult to do. For another, I don't actually grok most regex. Email addresses are just a horrifically stupidly defined spec.

I've gone ahead and committed #3, as it does fix the problem mentioned here. I am planning on a Drupal 7 port later this week that will likely involve some restructuring of the code. It is possible that we'll be able to switch to using a PHP-native ext/filter pattern for it, but I'm not sure. Definitely I am not going to try and break the Drupal 6 version at this point. :-)

Thanks, dboulet!

MRushton’s picture

Crell, email address regular expressions are not that complicated if you simply break it into individual components. I've done so in the article linked to by rkendall (http://squiloople.com/2009/12/20/email-address-validation/). And it's certainly not pages long:

To validate according to RFC 5321, allowing dot-atom and quoted string local parts, and domain name and domain literal domains, a regular expression of just 381 characters is needed:

/^(?>([!#-\'*+\/-9=?^-~-]+)(?>\.(?1))*|"(?>[ !#-\[\]-~]|\\\[ -~])*")@(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?2)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?3)){7}|(?!(?:.*[a-f0-9][:\]]){8,})((?3)(?>:(?3)){0,6})?::(?4)?))|(?>(?>IPv6:(?>(?3)(?>:(?3)){5}:|(?!(?:.*[a-f0-9]:){6,})(?5)?::(?>((?3)(?>:(?3)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?6)){3}))\])$/iD

To validate according to RFC 5322, allowing dot-atom, quoted string, and obsolete local parts, domain name and domain literal domains, and comments and folding white spaces, a regular expression of just 583 characters is needed:

/^((?>(?>(?>((?>[ ]+(?>\x0D\x0A[ ]+)*)?)(\((?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>(?1)\.(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD

Indeed, PHP's native function filter_var uses an older version of the first regular expression here. I've updated it since then. Personally, I think it's more useful than the second. Comments and folding white spaces are unnecessary, and even a nuisance. If you want even more functionality, perhaps you'd be interested in using the following class, also provided in my article, but copied here nonetheless in case you don't want to load a new page:

  /**
   * Squiloople
   *
   * LICENSE: Feel free to use and redistribute this code.
   *
   * @author Michael Rushton
   * @category Squiloople
   * @package Models
   * @subpackage Validators
   * @version 1.0
   * @copyright Copyright © 2010 Michael Rushton
   */

  namespace Validators;

  /**
   * Email Address Validator
   *
   * Validate email addresses according to the RFC 5321 or 5322 standard.
   */

  final class EmailAddressValidator
    {

      /**
       * The email address to validate
       *
       * @access private
       * @var string $_emailAddress
       */

      private $_emailAddress;

      /**
       * A quoted string local part is either allowed (true) or not (false)
       *
       * @access private
       * @var bool $_quotedString
       */

      private $_quotedString = false;

      /**
       * An obsolete local part is either allowed (true) or not (false)
       *
       * @access private
       * @var bool $_obsolete
       */

      private $_obsolete = false;

      /**
       * A domain literal domain is either allowed (true) or not (false)
       *
       * @access private
       * @var bool $_domainLiteral
       */

      private $_domainLiteral = false;

      /**
       * Comments and folding white spaces are either allowed (true) or not (false)
       *
       * @access private
       * @var bool $_cfws
       */

      private $_cfws = false;

      /**
       * Set the email address and turn on the relevant standard if required
       *
       * @access public
       * @param string $emailAddress
       * @param integer $standard
       */

      public function __construct($emailAddress, $standard = null)
        {

          // Set the email address

          $this->_emailAddress = $emailAddress;

          // Turn on the RFC 5321 standard if requested

          if ($standard == 5321)
            :
              return $this->setStandard5321();
          endif;

          // Otherwise turn on the RFC 5322 standard if requested

          if ($standard == 5322)
            :
              return $this->setStandard5322();
          endif;

      }

      /**
       * Call the constructor fluently
       *
       * @access public
       * @static
       * @param string $emailAddress
       * @param integer $standard
       * @return Validators\EmailAddressValidator
       */

      public static function setEmailAddress($emailAddress, $standard = null)
        {
          return new self($emailAddress, $standard);
      }

      /**
       * Validate the email address according to RFC 5321 and return itself
       *
       * @access public
       * @param bool $allow
       * @return Validators\EmailAddressValidator
       */

      public function setStandard5321($allow = true)
        {

          // A quoted string local part is either allowed (true) or not (false)

          $this->_quotedString = $allow;

          // A domain literal domain is either allowed (true) or not (false)

          $this->_domainLiteral = $allow;

          // Return itself

          return $this;

      }

      /**
       * Validate the email address according to RFC 5322 and return itself
       *
       * @access public
       * @param bool $allow
       * @return Validators\EmailAddressValidator
       */

      public function setStandard5322($allow = true)
        {

          // An obsolete local part is either allowed (true) or not (false)

          $this->_obsolete = $allow;

          // A domain literal domain is either allowed (true) or not (false)

          $this->_domainLiteral = $allow;

          // Comments and folding white spaces are either allowed (true) or not (false)

          $this->_cfws = $allow;

          // Return itself

          return $this;

      }

      /**
       * Either allow (true) or disallow (false) a quoted string local part and return itself
       *
       * @access public
       * @param bool $allow
       * @return Validators\EmailAddressValidator
       */

      public function setQuotedString($allow = true)
        {

          // Either allow (true) or disallow (false) a quoted string local part

          $this->_quotedString = $allow;

          // Return itself

          return $this;

      }

      /**
       * Either allow (true) or disallow (false) an obsolete local part and return itself
       *
       * @access public
       * @param bool $allow
       * @return Validators\EmailAddressValidator
       */

      public function setObsolete($allow = true)
        {

          // Either allow (true) or disallow (false) an obsolete local part

          $this->_obsolete = $allow;

          // Return itself

          return $this;

      }

      /**
       * Either allow (true) or disallow (false) a domain literal domain and return itself
       *
       * @access public
       * @param bool $allow
       * @return Validators\EmailAddressValidator
       */

      public function setDomainLiteral($allow = true)
        {

          // Either allow (true) or disallow (false) a domain literal domain

          $this->_domainLiteral = $allow;

          // Return itself

          return $this;

      }

      /**
       * Either allow (true) or disallow (false) comments and folding white spaces and return itself
       *
       * @access public
       * @param bool $allow
       * @return Validators\EmailAddressValidator
       */

      public function setCFWS($allow = true)
        {

          // Either allow (true) or disallow (false) comments and folding white spaces

          $this->_cfws = $allow;

          // Return itself

          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-\x7F]))*'
            . $this->_getFWS()
            . '")(?:'
            . $this->_getCFWS()
            . '\.'
            . $this->_getCFWS()
            . '(?1))*';

      }

      /**
       * Return the regular expression for a domain name domain
       *
       * @access private
       * @return string
       */

      private function _getDomainName()
        {

          return '([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)(?:'
            . $this->_getCFWS()
            . '\.'
            . $this->_getCFWS()
            . '(?2)){0,126}';

      }

      /**
       * Return the regular expression for an IPv6 address
       *
       * @access private
       * @return string
       */

      private function _getIPv6()
        {
          return '([a-f0-9]{1,4})(?::(?3)){7}|(?!(?:.*[a-f0-9][:\]]){8,})((?3)(?::(?3)){0,6})?::(?4)?';
      }

      /**
       * Return the regular expression for an IPv4-mapped IPv6 address
       *
       * @access private
       * @return string
       */

      private function _getIPv6v4()
        {
          return '(?3)(?::(?3)){5}:|(?!(?:.*[a-f0-9]:){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][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\.(?6)){3}';
      }

      /**
       * Return the regular expression for a domain literal domain
       *
       * @access private
       * @return string
       */

      private function _getDomainLiteral()
        {

          return '\[(?:(?:IPv6:(?:'
            . $this->_getIPv6()
            . '))|(?:(?:IPv6:(?:'
            . $this->_getIPv6v4()
            . '))?'
            . $this->_getIPv4()
            . '))\]';

      }

      /**
       * Return either the regular expression for folding white spaces or its backreference if allowed
       *
       * @access private
       * @var bool $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)' : '(?(?:[	 ]+(?:\x0D\x0A[	 ]+)*)?)';
          endif;

      }

      /**
       * Return the regular expression for comments
       *
       * @access private
       * @return string
       */

      private function _getComments()
        {

          return '(?\((?:'
            . $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 if allowed
       *
       * @access private
       * @var bool $define
       * @return string
       */

      private function _getCFWS($define = false)
        {

          // Return the backreference if $define is set to false

          if ($this->_cfws && !$define)
            :
              return '(?P>cfws)';
          endif;

          // Otherwise return the regular expression

          if ($this->_cfws)
            :

              return '(?(?:(?:(?:'
                . $this->_getFWS(true)
                . $this->_getComments()
                . ')+'
                . $this->_getFWS()
                . ')|'
                . $this->_getFWS()
                . ')?)';

          endif;

      }

      /**
       * 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();
          endif;

          // Otherwise a local part may be either a dot atom or a quoted string if the latter is allowed

          if ($this->_quotedString)
            :
              return '(?:' . $this->_getDotAtom() . '|' . $this->_getQuotedString() . ')';
          endif;

          // Otherwise the local part may only 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 may be either a domain name or a domain literal if the latter is allowed

          if ($this->_domainLiteral)
            :
              return '(?:' . $this->_getDomainName() . '|' . $this->_getDomainLiteral() . ')';
          endif;

          // Otherwise the domain must be a domain name

          return $this->_getDomainName();

      }

      /**
       * Check to see if the domain can be resolved to MX RRs
       *
       * @access private
       * @param array $domain
       * @return bool
       */

      private function _verifyDomain($domain)
        {

          // Return false if the domain cannot be resolved to MX RRs

          if (!checkdnsrr(end($domain), 'MX'))
            :
              return false;
          endif;

          // Otherwise return true

          return true;

      }

      /**
       * Perform the validation check on the email address's syntax and, if required, call _verifyDomain()
       *
       * @access public
       * @param bool $verify
       * @return bool|integer
       */

      public function validate($verify = false)
        {

          // Return false if the email address has an incorrect syntax

          if (!preg_match(

              '/^'
            . $this->_getCFWS()
            . $this->_getLocalPart()
            . $this->_getCFWS()
            . '@'
            . $this->_getCFWS()
            . $this->_getDomain()
            . $this->_getCFWS(true)
            . '$/isD'
            , $this->_emailAddress

          ))
            :
              return false;
          endif;

          // Otherwise check to see if the domain can be resolved to MX RRs if required

          if ($verify)
            :

              // Return 0 if the domain cannot be resolved to MX RRs

              if (!$this->_verifyDomain(explode('@', $this->_emailAddress)))
                :
                  return 0;
              endif;

              // Otherwise return true

              return true;

          endif;

          // Otherwise return 1

          return 1;

      }

  }
Crell’s picture

MRushton: Please do not keep editing and resaving your comment, or whatever it is that's causing it to show up as "new" again every day. That is very rude. The regex for invisimail is not changing for Drupal 6. It will not change for Drupal 7 either until and unless we have a test suite in place and viable test cases that fail without changing it. This issue is closed.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.