From c8e7f01dfb3a97c36df569e954c4e9e279b8fffc Mon Sep 17 00:00:00 2001 From: Eduardo Gulias Davis Date: Sun, 26 Jul 2020 16:15:50 +0200 Subject: [PATCH] Parsing comments, at last! --- .gitignore | 1 + EmailValidator/Parser/Comment.php | 40 ++++++----- EmailValidator/Parser/CommentStrategy.php | 18 +++++ EmailValidator/Parser/DomainComment.php | 37 ++++++++++ EmailValidator/Parser/DomainPart.php | 70 ++++++++----------- EmailValidator/Parser/LocalComment.php | 34 +++++++++ EmailValidator/Parser/LocalPart.php | 17 ++--- Tests/EmailValidator/LexerTokensTest.php | 2 +- .../Validation/RFCValidationTest.php | 19 ++--- 9 files changed, 153 insertions(+), 85 deletions(-) create mode 100644 EmailValidator/Parser/CommentStrategy.php create mode 100644 EmailValidator/Parser/DomainComment.php create mode 100644 EmailValidator/Parser/LocalComment.php diff --git a/.gitignore b/.gitignore index 09295a6..fb2edef 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ composer.lock report/ vendor/ .phpunit* +.vscode diff --git a/EmailValidator/Parser/Comment.php b/EmailValidator/Parser/Comment.php index cb0e8fa..fc90ad3 100644 --- a/EmailValidator/Parser/Comment.php +++ b/EmailValidator/Parser/Comment.php @@ -3,24 +3,31 @@ namespace Egulias\EmailValidator\Parser; use Egulias\EmailValidator\EmailLexer; -use Egulias\EmailValidator\Result\Result; use Egulias\EmailValidator\Result\ValidEmail; use Egulias\EmailValidator\Warning\CFWSNearAt; use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Parser\CommentStrategy; use Egulias\EmailValidator\Result\Reason\ExpectingATEXT; use Egulias\EmailValidator\Result\Reason\UnclosedComment; use Egulias\EmailValidator\Result\Reason\UnOpenedComment; use Egulias\EmailValidator\Warning\Comment as WarningComment; - class Comment extends Parser { - private $MopenedParenthesis = 0; + //change to private when removed from parent parser + protected $openedParenthesis = 0; + private $commentStrategy; + + public function __construct(EmailLexer $lexer, CommentStrategy $commentStrategy) + { + $this->lexer = $lexer; + $this->commentStrategy = $commentStrategy; + } public function parse($str) { if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { - $this->MopenedParenthesis++; + $this->openedParenthesis++; if($this->noClosingParenthesis()) { return new InvalidEmail(new UnclosedComment(), $this->lexer->token['value']); } @@ -31,30 +38,31 @@ class Comment extends Parser } $this->warnings[WarningComment::CODE] = new WarningComment(); - while (!$this->lexer->isNextToken(EmailLexer::S_AT)) {//!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) { + + $moreTokens = true; + while ($this->commentStrategy->exitCondition($this->lexer, $this->openedParenthesis) && $moreTokens){ + if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) { - $this->MopenedParenthesis++; + $this->openedParenthesis++; } $this->warnEscaping(); if($this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) { - $this->MopenedParenthesis--; + $this->openedParenthesis--; } - $this->lexer->moveNext(); + $moreTokens = $this->lexer->moveNext(); } - if($this->MopenedParenthesis >= 1) { + if($this->openedParenthesis >= 1) { return new InvalidEmail(new UnclosedComment(), $this->lexer->token['value']); - } else if ($this->MopenedParenthesis < 0) { + } else if ($this->openedParenthesis < 0) { return new InvalidEmail(new UnOpenedComment(), $this->lexer->token['value']); } - if (!$this->lexer->isNextToken(EmailLexer::S_AT)) { - return new InvalidEmail(new ExpectingATEXT('ATEX is not expected after closing comments'), $this->lexer->token['value']); - } + $finalValidations = $this->commentStrategy->endOfLoopValidations($this->lexer); - //You should always end at @ - $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt(); - return new ValidEmail(); + $this->warnings = array_merge($this->warnings, $this->commentStrategy->getWarnings()); + + return $finalValidations; } private function noClosingParenthesis() : bool diff --git a/EmailValidator/Parser/CommentStrategy.php b/EmailValidator/Parser/CommentStrategy.php new file mode 100644 index 0000000..d2186fd --- /dev/null +++ b/EmailValidator/Parser/CommentStrategy.php @@ -0,0 +1,18 @@ +isNextToken(EmailLexer::S_DOT))){ // || !$internalLexer->moveNext()) { + return false; + } + + return true; + } + + public function endOfLoopValidations(EmailLexer $lexer) : Result + { + //test for end of string + if (!$lexer->isNextToken(EmailLexer::S_DOT)) { + return new InvalidEmail(new ExpectingATEXT('DOT not found near CLOSEPARENTHESIS'), $lexer->token['value']); + } + //add warning + //Address is valid within the message but cannot be used unmodified for the envelope + return new ValidEmail(); + } + + public function getWarnings(): array + { + return []; + } +} \ No newline at end of file diff --git a/EmailValidator/Parser/DomainPart.php b/EmailValidator/Parser/DomainPart.php index a0e2ab6..194a84b 100644 --- a/EmailValidator/Parser/DomainPart.php +++ b/EmailValidator/Parser/DomainPart.php @@ -10,15 +10,12 @@ use Egulias\EmailValidator\Exception\CRLFAtTheEnd; use Egulias\EmailValidator\Exception\CRNoLF; use Egulias\EmailValidator\Exception\DomainHyphened; use Egulias\EmailValidator\Exception\DotAtEnd; -use Egulias\EmailValidator\Exception\DotAtStart; use Egulias\EmailValidator\Exception\ExpectingATEXT; use Egulias\EmailValidator\Exception\ExpectingDomainLiteralClose; use Egulias\EmailValidator\Exception\ExpectingDTEXT; -use Egulias\EmailValidator\Exception\NoDomainPart; -use Egulias\EmailValidator\Exception\UnopenedComment; use Egulias\EmailValidator\Result\InvalidEmail; use Egulias\EmailValidator\Result\Reason\DomainHyphened as ReasonDomainHyphened; -use Egulias\EmailValidator\Result\Reason\DotAtStart as ReasonDotAtStart; +use Egulias\EmailValidator\Result\Reason\DotAtStart; use Egulias\EmailValidator\Result\Reason\NoDomainPart as ReasonNoDomainPart; use Egulias\EmailValidator\Result\Result; use Egulias\EmailValidator\Result\ValidEmail; @@ -57,9 +54,12 @@ class DomainPart extends Parser } $domain = $this->doParseDomainPart(); + if ($domain->isInvalid()) { + return $domain; + } $prev = $this->lexer->getPrevious(); - $length = strlen($domain); + $length = strlen($this->domainPart); if ($prev['type'] === EmailLexer::S_DOT) { throw new DotAtEnd(); @@ -73,7 +73,6 @@ class DomainPart extends Parser if ($prev['type'] === EmailLexer::S_CR) { throw new CRLFAtTheEnd(); } - $this->domainPart = $domain; return new ValidEmail(); } @@ -92,7 +91,6 @@ class DomainPart extends Parser if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment(); - $this->parseDomainComments(); } return new ValidEmail(); } @@ -113,7 +111,7 @@ class DomainPart extends Parser private function checkInvalidTokensAfterAT() : Result { if ($this->lexer->token['type'] === EmailLexer::S_DOT) { - return new InvalidEmail(new ReasonDotAtStart(), $this->lexer->token['value']); + return new InvalidEmail(new DotAtStart(), $this->lexer->token['value']); } if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) { return new InvalidEmail(new ReasonDomainHyphened('After AT'), $this->lexer->token['value']); @@ -176,32 +174,33 @@ class DomainPart extends Parser } } - /** - * @return string - */ - protected function doParseDomainPart() + protected function parseComments() + { + $commentParser = new Comment($this->lexer, new DomainComment()); + $result = $commentParser->parse('remove'); + if($result->isInvalid()) { + return $result; + } + + $this->warnings = array_merge($this->warnings, $commentParser->getWarnings()); + return $result; + } + + protected function doParseDomainPart() : Result { $domain = ''; - $openedParenthesis = 0; do { $prev = $this->lexer->getPrevious(); $this->checkNotAllowedChars($this->lexer->token); - if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { - $this->parseComments(); - $openedParenthesis += $this->getOpenedParenthesis(); - $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 UnopenedComment(); - } else { - $openedParenthesis--; + if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS || + $this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS ) { + $commentsResult = $this->parseComments(); + + //Invalid comment parsing + if($commentsResult->isInvalid()) { + return $commentsResult; } } @@ -222,7 +221,8 @@ class DomainPart extends Parser $this->lexer->moveNext(); } while (null !== $this->lexer->token['type']); - return $domain; + $this->domainPart = $domain; + return new ValidEmail(); } private function checkNotAllowedChars(array $token) @@ -413,20 +413,6 @@ class DomainPart extends Parser } } - protected function parseDomainComments() - { - $this->isUnclosedComment(); - while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) { - $this->warnEscaping(); - $this->lexer->moveNext(); - } - - $this->lexer->moveNext(); - if ($this->lexer->isNextToken(EmailLexer::S_DOT)) { - throw new ExpectingATEXT(); - } - } - protected function addTLDWarnings() { if ($this->warnings[DomainLiteral::CODE]) { diff --git a/EmailValidator/Parser/LocalComment.php b/EmailValidator/Parser/LocalComment.php new file mode 100644 index 0000000..bdf5ef6 --- /dev/null +++ b/EmailValidator/Parser/LocalComment.php @@ -0,0 +1,34 @@ +isNextToken(EmailLexer::S_AT); + } + + public function endOfLoopValidations(EmailLexer $lexer) : Result + { + if (!$lexer->isNextToken(EmailLexer::S_AT)) { + return new InvalidEmail(new ExpectingATEXT('ATEX is not expected after closing comments'), $lexer->token['value']); + } + $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt(); + return new ValidEmail(); + } + + public function getWarnings(): array + { + return $this->warnings; + } +} \ No newline at end of file diff --git a/EmailValidator/Parser/LocalPart.php b/EmailValidator/Parser/LocalPart.php index dfd184c..fc264c5 100644 --- a/EmailValidator/Parser/LocalPart.php +++ b/EmailValidator/Parser/LocalPart.php @@ -97,10 +97,7 @@ class LocalPart extends Parser $foldingWS = new FoldingWhiteSpace($this->lexer); $resultFWS = $foldingWS->parse('remove'); if ($resultFWS->isValid()) { - $warns = $foldingWS->getWarnings(); - foreach ($warns as $code => $dWarning) { - $this->warnings[$code] = $dWarning; - } + $this->warnings = array_merge($this->warnings, $foldingWS->getWarnings()); } return $resultFWS; } @@ -114,25 +111,19 @@ class LocalPart extends Parser { $dquoteParser = new DoubleQuote($this->lexer); $parseAgain = $dquoteParser->parse("remove useless arg"); - $warns = $dquoteParser->getWarnings(); - foreach ($warns as $code => $dWarning) { - $this->warnings[$code] = $dWarning; - } + $this->warnings = array_merge($this->warnings, $dquoteParser->getWarnings()); return $parseAgain; } protected function parseComments() { - $commentParser = new Comment($this->lexer); + $commentParser = new Comment($this->lexer, new LocalComment()); $result = $commentParser->parse('remove'); + $this->warnings = array_merge($this->warnings, $commentParser->getWarnings()); if($result->isInvalid()) { return $result; } - $warns = $commentParser->getWarnings(); - foreach ($warns as $code => $dWarning) { - $this->warnings[$code] = $dWarning; - } return $result; } } \ No newline at end of file diff --git a/Tests/EmailValidator/LexerTokensTest.php b/Tests/EmailValidator/LexerTokensTest.php index 2dc521c..1e54198 100644 --- a/Tests/EmailValidator/LexerTokensTest.php +++ b/Tests/EmailValidator/LexerTokensTest.php @@ -6,6 +6,6 @@ class LexerTokensTest extends TestCase { public function testToken() { - $this->markTestIncomplete("implement"); + $this->markTestIncomplete("implement beter lexer tokens"); } } \ No newline at end of file diff --git a/Tests/EmailValidator/Validation/RFCValidationTest.php b/Tests/EmailValidator/Validation/RFCValidationTest.php index 53aa017..172d285 100644 --- a/Tests/EmailValidator/Validation/RFCValidationTest.php +++ b/Tests/EmailValidator/Validation/RFCValidationTest.php @@ -4,7 +4,6 @@ namespace Egulias\Tests\EmailValidator\Validation; use PHPUnit\Framework\TestCase; use Egulias\EmailValidator\EmailLexer; -use Egulias\EmailValidator\EmailValidator; use Egulias\EmailValidator\Warning\Comment; use Egulias\EmailValidator\Exception\CRNoLF; use Egulias\EmailValidator\Exception\DotAtEnd; @@ -12,7 +11,6 @@ use Egulias\EmailValidator\Warning\CFWSNearAt; use Egulias\EmailValidator\Result\InvalidEmail; use Egulias\EmailValidator\Warning\CFWSWithFWS; use Egulias\EmailValidator\Warning\IPV6BadChar; -use Egulias\EmailValidator\Exception\DotAtStart; use Egulias\EmailValidator\Warning\IPV6ColonEnd; use Egulias\EmailValidator\Warning\LabelTooLong; use Egulias\EmailValidator\Warning\LocalTooLong; @@ -21,22 +19,17 @@ use Egulias\EmailValidator\Warning\DomainLiteral; use Egulias\EmailValidator\Warning\DomainTooLong; use Egulias\EmailValidator\Warning\IPV6MaxGroups; use Egulias\EmailValidator\Warning\ObsoleteDTEXT; -use Egulias\EmailValidator\Exception\NoDomainPart; use Egulias\EmailValidator\Warning\AddressLiteral; use Egulias\EmailValidator\Warning\IPV6ColonStart; use Egulias\EmailValidator\Warning\IPV6Deprecated; use Egulias\EmailValidator\Warning\IPV6GroupCount; use Egulias\EmailValidator\Exception\ConsecutiveAt; use Egulias\EmailValidator\Warning\IPV6DoubleColon; -use Egulias\EmailValidator\Exception\AtextAfterCFWS; use Egulias\EmailValidator\Exception\ConsecutiveDot; use Egulias\EmailValidator\Exception\DomainHyphened; -use Egulias\EmailValidator\Exception\ExpectingATEXT; use Egulias\EmailValidator\Exception\ExpectingDTEXT; use Egulias\EmailValidator\Validation\RFCValidation; -use Egulias\EmailValidator\Exception\UnclosedComment; -use Egulias\EmailValidator\Exception\UnopenedComment as ExceptionUnopenedComment; -use Egulias\EmailValidator\Result\Reason\AtextAfterCFWS as ReasonAtextAfterCFWS; +use Egulias\EmailValidator\Result\Reason\AtextAfterCFWS; use Egulias\EmailValidator\Result\Reason\ConsecutiveDot as ReasonConsecutiveDot; use Egulias\EmailValidator\Result\Reason\DotAtEnd as ReasonDotAtEnd; use Egulias\EmailValidator\Result\Reason\NoLocalPart; @@ -217,14 +210,14 @@ class RFCValidationTest extends TestCase ], [new InvalidEmail(new UnOpenedComment(), ')'), 'comment)example@localhost'], [new InvalidEmail(new UnOpenedComment(), ')'), 'example(comment))@localhost'], - [new ExceptionUnopenedComment, 'example@comment)localhost'], - [new ExceptionUnopenedComment, 'example@localhost(comment))'], - [new ExceptionUnopenedComment, 'example@(comment))example.com'], + [new InvalidEmail(new UnOpenedComment(), ')'), 'example@comment)localhost'], + [new InvalidEmail(new UnOpenedComment(), ')'), 'example@localhost(comment))'], + [new InvalidEmail(new UnOpenedComment(), 'com'), 'example@(comment))example.com'], //This was the original. But atext is not allowed after \n //array(EmailValidator::ERR_EXPECTING_ATEXT, "exampl\ne@example.co.uk"), - [new InvalidEmail(new ReasonAtextAfterCFWS(), "\n"), "exampl\ne@example.co.uk"], + [new InvalidEmail(new AtextAfterCFWS(), "\n"), "exampl\ne@example.co.uk"], [new ExpectingDTEXT(), "example@[[]"], - [new InvalidEmail(new ReasonAtextAfterCFWS(), "\t"), "exampl\te@example.co.uk"], + [new InvalidEmail(new AtextAfterCFWS(), "\t"), "exampl\te@example.co.uk"], [new CRNoLF(), "example@exa\rmple.co.uk"], [new CRNoLF(), "example@[\r]"], [new InvalidEmail(new ReasonCRNoLF(), "\r"), "exam\rple@example.co.uk"],