Parsing comments, at last!

This commit is contained in:
Eduardo Gulias Davis
2020-07-26 16:15:50 +02:00
parent 61798f9f1b
commit c8e7f01dfb
9 changed files with 153 additions and 85 deletions
+1
View File
@@ -3,3 +3,4 @@ composer.lock
report/
vendor/
.phpunit*
.vscode
+24 -16
View File
@@ -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
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Result\Result;
interface CommentStrategy
{
/**
* Return "true" to continue, "false" to exit
*/
public function exitCondition(EmailLexer $lexer, int $openedParenthesis) : bool;
public function endOfLoopValidations(EmailLexer $lexer) : Result;
public function getWarnings() : array;
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Result\Result;
use Egulias\EmailValidator\Result\ValidEmail;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
class DomainComment implements CommentStrategy
{
public function exitCondition(EmailLexer $lexer, int $openedParenthesis) : bool
{
if (($openedParenthesis === 0 && $lexer->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 [];
}
}
+28 -42
View File
@@ -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]) {
+34
View File
@@ -0,0 +1,34 @@
<?php
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\Result\Reason\ExpectingATEXT;
class LocalComment implements CommentStrategy
{
private $warnings = [];
public function exitCondition(EmailLexer $lexer, int $openedParenthesis) : bool
{
return !$lexer->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;
}
}
+4 -13
View File
@@ -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;
}
}
+1 -1
View File
@@ -6,6 +6,6 @@ class LexerTokensTest extends TestCase
{
public function testToken()
{
$this->markTestIncomplete("implement");
$this->markTestIncomplete("implement beter lexer tokens");
}
}
@@ -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"],