Skip to content

Commit

Permalink
Message-id validator (#290)
Browse files Browse the repository at this point in the history
* scafolding for MessageID validation

* Improve domain valid tokens

* Improved EmailParser to remove leaked logic. User of lexer recorder within parsers.

* MessageIDParser passing tests.

* change left for right, which is the right one

* psaml errors

* Better naming

* comments are not allowed in IDLeft for message-id

* Suppress psalm inheritance over tokens and dependencies

* Update src/Parser.php

Co-authored-by: Alexander M. Turek <me@derrabus.de>

* Update src/Parser.php

Co-authored-by: Alexander M. Turek <me@derrabus.de>

* improve parser from comments

Co-authored-by: Alexander M. Turek <me@derrabus.de>
  • Loading branch information
egulias and derrabus authored Mar 6, 2021
1 parent 451b438 commit 8e526a5
Show file tree
Hide file tree
Showing 17 changed files with 557 additions and 240 deletions.
197 changes: 99 additions & 98 deletions composer.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/EmailLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class EmailLexer extends AbstractLexer
*
* @var array
*
* @psalm-suppress NonInvariantDocblockPropertyType
* @psalm-var array{value:string, type:null|int, position:int}
* @psalm-suppress NonInvariantDocblockPropertyType
*/
Expand Down
93 changes: 21 additions & 72 deletions src/EmailParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,12 @@
use Egulias\EmailValidator\Result\ValidEmail;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Warning\EmailTooLong;
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
use Egulias\EmailValidator\Result\Reason\NoLocalPart;

class EmailParser
class EmailParser extends Parser
{
const EMAIL_MAX_LENGTH = 254;

/**
* @var array
*/
protected $warnings = [];

/**
* @var string
*/
Expand All @@ -30,81 +24,54 @@ class EmailParser
* @var string
*/
protected $localPart = '';
/**
* @var EmailLexer
*/
protected $lexer;

public function __construct(EmailLexer $lexer)
{
$this->lexer = $lexer;
}

public function parse(string $str) : Result
{
$this->lexer->setInput($str);
$result = parent::parse($str);

$this->addLongEmailWarning($this->localPart, $this->domainPart);

return $result;
}

protected function preLeftParsing(): Result
{
if (!$this->hasAtToken()) {
return new InvalidEmail(new NoLocalPart(), $this->lexer->token["value"]);
}
return new ValidEmail();
}

$localPartResult = $this->processLocalPart();

if ($localPartResult->isInvalid()) {
return $localPartResult;
}

$domainPartResult = $this->processDomainPart();

if ($domainPartResult->isInvalid()) {
return $domainPartResult;
}

if ($this->lexer->hasInvalidTokens()) {
return new InvalidEmail(new ExpectingATEXT("Invalid tokens found"), $this->lexer->token["value"]);
}

$this->addLongEmailWarning($this->localPart, $this->domainPart);
protected function parseLeftFromAt(): Result
{
return $this->processLocalPart();
}

return new ValidEmail();
protected function parseRightFromAt(): Result
{
return $this->processDomainPart();
}

private function processLocalPart() : Result
{
$this->lexer->startRecording();
$localPartParser = new LocalPart($this->lexer);
$localPartResult = $localPartParser->parse();
$this->lexer->stopRecording();
$this->localPart = rtrim($this->lexer->getAccumulatedValues(), '@');
$this->localPart = $localPartParser->localPart();
$this->warnings = array_merge($localPartParser->getWarnings(), $this->warnings);

return $localPartResult;
}

private function processDomainPart() : Result
{
$this->lexer->clearRecorded();
$this->lexer->startRecording();
$domainPartParser = new DomainPart($this->lexer);
$domainPartResult = $domainPartParser->parse();
$this->lexer->stopRecording();
$this->domainPart = $this->lexer->getAccumulatedValues();
$this->domainPart = $domainPartParser->domainPart();
$this->warnings = array_merge($domainPartParser->getWarnings(), $this->warnings);

return $domainPartResult;
}

/**
* @return Warning\Warning[]
*/
public function getWarnings() : array
{
return $this->warnings;
}

/**
* @return string
*/
public function getDomainPart() : string
{
return $this->domainPart;
Expand All @@ -115,25 +82,7 @@ public function getLocalPart() : string
return $this->localPart;
}

/**
* @return bool
*/
protected function hasAtToken() : bool
{
$this->lexer->moveNext();
$this->lexer->moveNext();
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
return false;
}

return true;
}

/**
* @param string $localPart
* @param string $parsedDomainPart
*/
protected function addLongEmailWarning($localPart, $parsedDomainPart) : void
private function addLongEmailWarning(string $localPart, string $parsedDomainPart) : void
{
if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAIL_MAX_LENGTH) {
$this->warnings[EmailTooLong::CODE] = new EmailTooLong();
Expand Down
93 changes: 93 additions & 0 deletions src/MessageIDParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace Egulias\EmailValidator;

use Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Result\Result;
use Egulias\EmailValidator\Parser\IDLeftPart;
use Egulias\EmailValidator\Parser\IDRightPart;
use Egulias\EmailValidator\Result\ValidEmail;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Warning\EmailTooLong;
use Egulias\EmailValidator\Result\Reason\NoLocalPart;

class MessageIDParser extends Parser
{

const EMAILID_MAX_LENGTH = 254;

/**
* @var string
*/
protected $idLeft = '';

/**
* @var string
*/
protected $idRight = '';

public function parse(string $str) : Result
{
$result = parent::parse($str);

$this->addLongEmailWarning($this->idLeft, $this->idRight);

return $result;
}

protected function preLeftParsing(): Result
{
if (!$this->hasAtToken()) {
return new InvalidEmail(new NoLocalPart(), $this->lexer->token["value"]);
}
return new ValidEmail();
}

protected function parseLeftFromAt(): Result
{
return $this->processIDLeft();
}

protected function parseRightFromAt(): Result
{
return $this->processIDRight();
}

private function processIDLeft() : Result
{
$localPartParser = new IDLeftPart($this->lexer);
$localPartResult = $localPartParser->parse();
$this->idLeft = $localPartParser->localPart();
$this->warnings = array_merge($localPartParser->getWarnings(), $this->warnings);

return $localPartResult;
}

private function processIDRight() : Result
{
$domainPartParser = new IDRightPart($this->lexer);
$domainPartResult = $domainPartParser->parse();
$this->idRight = $domainPartParser->domainPart();
$this->warnings = array_merge($domainPartParser->getWarnings(), $this->warnings);

return $domainPartResult;
}

public function getLeftPart() : string
{
return $this->idLeft;
}

public function getRightPart() : string
{
return $this->idRight;
}

private function addLongEmailWarning(string $localPart, string $parsedDomainPart) : void
{
if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAILID_MAX_LENGTH) {
$this->warnings[EmailTooLong::CODE] = new EmailTooLong();
}
}
}
78 changes: 78 additions & 0 deletions src/Parser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

namespace Egulias\EmailValidator;

use Egulias\EmailValidator\Result\Result;
use Egulias\EmailValidator\Result\ValidEmail;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;

abstract class Parser
{
/**
* @var Warning\Warning[]
*/
protected $warnings = [];

/**
* @var EmailLexer
*/
protected $lexer;

/**
* id-left "@" id-right
*/
abstract protected function parseRightFromAt() : Result;
abstract protected function parseLeftFromAt() : Result;
abstract protected function preLeftParsing() : Result;


public function __construct(EmailLexer $lexer)
{
$this->lexer = $lexer;
}

public function parse(string $str) : Result
{
$this->lexer->setInput($str);

if ($this->lexer->hasInvalidTokens()) {
return new InvalidEmail(new ExpectingATEXT("Invalid tokens found"), $this->lexer->token["value"]);
}

$preParsingResult = $this->preLeftParsing();
if ($preParsingResult->isInvalid()) {
return $preParsingResult;
}

$localPartResult = $this->parseLeftFromAt();

if ($localPartResult->isInvalid()) {
return $localPartResult;
}

$domainPartResult = $this->parseRightFromAt();

if ($domainPartResult->isInvalid()) {
return $domainPartResult;
}

return new ValidEmail();
}

/**
* @return Warning\Warning[]
*/
public function getWarnings() : array
{
return $this->warnings;
}

protected function hasAtToken() : bool
{
$this->lexer->moveNext();
$this->lexer->moveNext();

return $this->lexer->token['type'] !== EmailLexer::S_AT;
}
}
2 changes: 1 addition & 1 deletion src/Parser/Comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use Egulias\EmailValidator\Result\Reason\UnOpenedComment;
use Egulias\EmailValidator\Warning\Comment as WarningComment;

class Comment extends Parser
class Comment extends PartParser
{
/**
* @var int
Expand Down
2 changes: 1 addition & 1 deletion src/Parser/DomainLiteral.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
use Egulias\EmailValidator\Result\Reason\UnusualElements;
use Egulias\EmailValidator\Warning\DomainLiteral as WarningDomainLiteral;

class DomainLiteral extends Parser
class DomainLiteral extends PartParser
{
public function parse() : Result
{
Expand Down
Loading

0 comments on commit 8e526a5

Please sign in to comment.