mirror of
https://github.com/egulias/EmailValidator.git
synced 2026-08-17 10:50:37 +00:00
Parsing comments, at last!
This commit is contained in:
@@ -3,3 +3,4 @@ composer.lock
|
||||
report/
|
||||
vendor/
|
||||
.phpunit*
|
||||
.vscode
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 [];
|
||||
}
|
||||
}
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"],
|
||||
|
||||
Reference in New Issue
Block a user