diff --git a/src/Egulias/EmailValidator/EmailValidator.php b/src/Egulias/EmailValidator/EmailValidator.php index d047dec..7e2b65f 100644 --- a/src/Egulias/EmailValidator/EmailValidator.php +++ b/src/Egulias/EmailValidator/EmailValidator.php @@ -33,6 +33,7 @@ class EmailValidator const ERR_FWS_CRLF_END = 149; const ERR_CR_NO_LF = 150; const ERR_DEPREC_REACHED = 151; + const ERR_UNOPENEDCOMMENT = 152; const RFC5321_TLD = 9; const RFC5321_TLDNUMERIC = 10; const RFC5321_QUOTEDSTRING = 11; diff --git a/src/Egulias/EmailValidator/Parser/DomainPart.php b/src/Egulias/EmailValidator/Parser/DomainPart.php index c4d0cf6..a15bf48 100644 --- a/src/Egulias/EmailValidator/Parser/DomainPart.php +++ b/src/Egulias/EmailValidator/Parser/DomainPart.php @@ -4,7 +4,6 @@ namespace Egulias\EmailValidator\Parser; use Egulias\EmailValidator\EmailLexer; -use Egulias\EmailValidator\Parser\Parser; use Egulias\EmailValidator\EmailValidator; class DomainPart extends Parser @@ -103,8 +102,8 @@ public function checkIPV6Tag($addressLiteral, $maxGroups = 8) protected function doParseDomainPart() { $domain = ''; + $openedParenthesis = 0; do { - $prev = $this->lexer->getPrevious(); if ($this->lexer->token['type'] === EmailLexer::S_SLASH) { @@ -112,8 +111,19 @@ protected function doParseDomainPart() } if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { - $this->parseComments(); + $this->parseComments($openedParenthesis); $this->lexer->moveNext(); + $tmpPrev = $this->lexer->getPrevious(); + if ($tmpPrev['type'] === EmailLexer::S_CLOSEPARENTHESIS) { + $openedParenthesis--; + } + } + if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) { + if ($openedParenthesis === 0) { + throw new \InvalidArgumentException('ERR_UNOPENEDCOMMENT'); + } else { + $openedParenthesis--; + } } $this->checkConsecutiveDots(); @@ -180,7 +190,7 @@ protected function doParseDomainLiteral() } if ($this->lexer->isNextToken(EmailLexer::S_CR)) { - throw new \InvalidArgumentException("ERR_CR_NO_LF"); + throw new \InvalidArgumentException('ERR_CR_NO_LF'); } if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) { $this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT; diff --git a/src/Egulias/EmailValidator/Parser/LocalPart.php b/src/Egulias/EmailValidator/Parser/LocalPart.php index 2f77164..4b768d9 100644 --- a/src/Egulias/EmailValidator/Parser/LocalPart.php +++ b/src/Egulias/EmailValidator/Parser/LocalPart.php @@ -4,7 +4,6 @@ use Egulias\EmailValidator\EmailLexer; use Egulias\EmailValidator\EmailValidator; -use \InvalidArgumentException; class LocalPart extends Parser { @@ -12,9 +11,9 @@ public function parse($localPart) { $parseDQuote = true; $closingQuote = false; + $openedParenthesis = 0; while ($this->lexer->token['type'] !== EmailLexer::S_AT && $this->lexer->token) { - if ($this->lexer->token['type'] === EmailLexer::S_DOT && !$this->lexer->getPrevious()) { throw new \InvalidArgumentException('ERR_DOT_START'); } @@ -25,13 +24,19 @@ public function parse($localPart) } if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { - $this->parseComments(); + $this->parseComments($openedParenthesis); + } + if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) { + if ($openedParenthesis === 0) { + throw new \InvalidArgumentException('ERR_UNOPENEDCOMMENT'); + } else { + $openedParenthesis--; + } } $this->checkConsecutiveDots(); - if ( - $this->lexer->token['type'] === EmailLexer::S_DOT && + if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_AT) ) { throw new \InvalidArgumentException('ERR_DOT_END'); @@ -82,7 +87,7 @@ protected function parseDoubleQuote() $this->lexer->moveNext(); if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) { - throw new InvalidArgumentException("ERR_EXPECTED_ATEXT"); + throw new \InvalidArgumentException('ERR_EXPECTED_ATEXT'); } } @@ -90,12 +95,12 @@ protected function parseDoubleQuote() if ($prev['type'] === EmailLexer::S_BACKSLASH) { if (!$this->checkDQUOTE(false)) { - throw new \InvalidArgumentException("ERR_UNCLOSED_DQUOTE"); + throw new \InvalidArgumentException('ERR_UNCLOSED_DQUOTE'); } } if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) { - throw new \InvalidArgumentException("ERR_EXPECED_AT"); + throw new \InvalidArgumentException('ERR_EXPECED_AT'); } return $parseAgain; diff --git a/src/Egulias/EmailValidator/Parser/Parser.php b/src/Egulias/EmailValidator/Parser/Parser.php index b66279e..9d60d97 100644 --- a/src/Egulias/EmailValidator/Parser/Parser.php +++ b/src/Egulias/EmailValidator/Parser/Parser.php @@ -20,7 +20,7 @@ public function getWarnings() return $this->warnings; } - abstract function parse($str); + abstract public function parse($str); /** * validateQuotedPair @@ -36,14 +36,18 @@ protected function validateQuotedPair() } /** - * @return string the the comment - * @throws \InvalidArgumentException + * @param int $openedParenthesis + * @return string the comment */ - protected function parseComments() + protected function parseComments(&$openedParenthesis = 0) { + $openedParenthesis++; $this->isUnclosedComment(); $this->warnings[] = EmailValidator::CFWS_COMMENT; while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) { + if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) { + $openedParenthesis++; + } $this->warnEscaping(); $this->lexer->moveNext(); } @@ -75,11 +79,11 @@ protected function parseFWS() $this->checkCRLFInFWS(); if ($this->lexer->token['type'] === EmailLexer::S_CR) { - throw new \InvalidArgumentException("ERR_CR_NO_LF"); + throw new \InvalidArgumentException('ERR_CR_NO_LF'); } if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) { - throw new \InvalidArgumentException("ERR_ATEXT_AFTER_CFWS"); + throw new \InvalidArgumentException('ERR_ATEXT_AFTER_CFWS'); } if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) { @@ -181,10 +185,10 @@ protected function checkCRLFInFWS() return; } if ($this->lexer->isNextToken(EmailLexer::CRLF)) { - throw new \InvalidArgumentException("ERR_FWS_CRLF_X2"); + throw new \InvalidArgumentException('ERR_FWS_CRLF_X2'); } if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) { - throw new \InvalidArgumentException("ERR_FWS_CRLF_END"); + throw new \InvalidArgumentException('ERR_FWS_CRLF_END'); } } } diff --git a/tests/egulias/Tests/EmailValidator/EmailValidatorTest.php b/tests/egulias/Tests/EmailValidator/EmailValidatorTest.php index 1491df7..03c08d1 100644 --- a/tests/egulias/Tests/EmailValidator/EmailValidatorTest.php +++ b/tests/egulias/Tests/EmailValidator/EmailValidatorTest.php @@ -152,6 +152,11 @@ public function getInvalidEmailsWithErrors() array(EmailValidator::ERR_DOT_END, 'example@localhost.'), array(EmailValidator::ERR_DOT_END, 'example.@example.co.uk'), array(EmailValidator::ERR_UNCLOSEDCOMMENT, '(example@localhost'), + array(EmailValidator::ERR_UNOPENEDCOMMENT, 'comment)example@localhost'), + array(EmailValidator::ERR_UNOPENEDCOMMENT, 'example(comment))@localhost'), + array(EmailValidator::ERR_UNOPENEDCOMMENT, 'example@comment)localhost'), + array(EmailValidator::ERR_UNOPENEDCOMMENT, 'example@localhost(comment))'), + array(EmailValidator::ERR_UNOPENEDCOMMENT, 'example@(comment))example.com'), array(EmailValidator::ERR_UNCLOSEDQUOTEDSTR, '"example@localhost'), array(EmailValidator::ERR_EXPECTING_ATEXT, 'exa"mple@localhost'), //This was the original. But atext is not allowed after \n