You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
6.7 KiB
249 lines
6.7 KiB
<?php |
|
|
|
namespace Egulias\EmailValidator\Parser; |
|
|
|
use Egulias\EmailValidator\EmailLexer; |
|
use Egulias\EmailValidator\Exception\AtextAfterCFWS; |
|
use Egulias\EmailValidator\Exception\ConsecutiveDot; |
|
use Egulias\EmailValidator\Exception\CRLFAtTheEnd; |
|
use Egulias\EmailValidator\Exception\CRLFX2; |
|
use Egulias\EmailValidator\Exception\CRNoLF; |
|
use Egulias\EmailValidator\Exception\ExpectingQPair; |
|
use Egulias\EmailValidator\Exception\ExpectingATEXT; |
|
use Egulias\EmailValidator\Exception\ExpectingCTEXT; |
|
use Egulias\EmailValidator\Exception\UnclosedComment; |
|
use Egulias\EmailValidator\Exception\UnclosedQuotedString; |
|
use Egulias\EmailValidator\Warning\CFWSNearAt; |
|
use Egulias\EmailValidator\Warning\CFWSWithFWS; |
|
use Egulias\EmailValidator\Warning\Comment; |
|
use Egulias\EmailValidator\Warning\QuotedPart; |
|
use Egulias\EmailValidator\Warning\QuotedString; |
|
|
|
abstract class Parser |
|
{ |
|
/** |
|
* @var array |
|
*/ |
|
protected $warnings = []; |
|
|
|
/** |
|
* @var EmailLexer |
|
*/ |
|
protected $lexer; |
|
|
|
/** |
|
* @var int |
|
*/ |
|
protected $openedParenthesis = 0; |
|
|
|
public function __construct(EmailLexer $lexer) |
|
{ |
|
$this->lexer = $lexer; |
|
} |
|
|
|
/** |
|
* @return \Egulias\EmailValidator\Warning\Warning[] |
|
*/ |
|
public function getWarnings() |
|
{ |
|
return $this->warnings; |
|
} |
|
|
|
/** |
|
* @param string $str |
|
*/ |
|
abstract public function parse($str); |
|
|
|
/** @return int */ |
|
public function getOpenedParenthesis() |
|
{ |
|
return $this->openedParenthesis; |
|
} |
|
|
|
/** |
|
* validateQuotedPair |
|
*/ |
|
protected function validateQuotedPair() |
|
{ |
|
if (!($this->lexer->token['type'] === EmailLexer::INVALID |
|
|| $this->lexer->token['type'] === EmailLexer::C_DEL)) { |
|
throw new ExpectingQPair(); |
|
} |
|
|
|
$this->warnings[QuotedPart::CODE] = |
|
new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']); |
|
} |
|
|
|
protected function parseComments() |
|
{ |
|
$this->openedParenthesis = 1; |
|
$this->isUnclosedComment(); |
|
$this->warnings[Comment::CODE] = new Comment(); |
|
while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) { |
|
if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) { |
|
$this->openedParenthesis++; |
|
} |
|
$this->warnEscaping(); |
|
$this->lexer->moveNext(); |
|
} |
|
|
|
$this->lexer->moveNext(); |
|
if ($this->lexer->isNextTokenAny(array(EmailLexer::GENERIC, EmailLexer::S_EMPTY))) { |
|
throw new ExpectingATEXT(); |
|
} |
|
|
|
if ($this->lexer->isNextToken(EmailLexer::S_AT)) { |
|
$this->warnings[CFWSNearAt::CODE] = new CFWSNearAt(); |
|
} |
|
} |
|
|
|
/** |
|
* @return bool |
|
*/ |
|
protected function isUnclosedComment() |
|
{ |
|
try { |
|
$this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS); |
|
return true; |
|
} catch (\RuntimeException $e) { |
|
throw new UnclosedComment(); |
|
} |
|
} |
|
|
|
protected function parseFWS() |
|
{ |
|
$previous = $this->lexer->getPrevious(); |
|
|
|
$this->checkCRLFInFWS(); |
|
|
|
if ($this->lexer->token['type'] === EmailLexer::S_CR) { |
|
throw new CRNoLF(); |
|
} |
|
|
|
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) { |
|
throw new AtextAfterCFWS(); |
|
} |
|
|
|
if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) { |
|
throw new ExpectingCTEXT(); |
|
} |
|
|
|
if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous['type'] === EmailLexer::S_AT) { |
|
$this->warnings[CFWSNearAt::CODE] = new CFWSNearAt(); |
|
} else { |
|
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); |
|
} |
|
} |
|
|
|
protected function checkConsecutiveDots() |
|
{ |
|
if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) { |
|
throw new ConsecutiveDot(); |
|
} |
|
} |
|
|
|
/** |
|
* @return bool |
|
*/ |
|
protected function isFWS() |
|
{ |
|
if ($this->escaped()) { |
|
return false; |
|
} |
|
|
|
if ($this->lexer->token['type'] === EmailLexer::S_SP || |
|
$this->lexer->token['type'] === EmailLexer::S_HTAB || |
|
$this->lexer->token['type'] === EmailLexer::S_CR || |
|
$this->lexer->token['type'] === EmailLexer::S_LF || |
|
$this->lexer->token['type'] === EmailLexer::CRLF |
|
) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/** |
|
* @return bool |
|
*/ |
|
protected function escaped() |
|
{ |
|
$previous = $this->lexer->getPrevious(); |
|
|
|
if ($previous && $previous['type'] === EmailLexer::S_BACKSLASH |
|
&& |
|
$this->lexer->token['type'] !== EmailLexer::GENERIC |
|
) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/** |
|
* @return bool |
|
*/ |
|
protected function warnEscaping() |
|
{ |
|
if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) { |
|
return false; |
|
} |
|
|
|
if ($this->lexer->isNextToken(EmailLexer::GENERIC)) { |
|
throw new ExpectingATEXT(); |
|
} |
|
|
|
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) { |
|
return false; |
|
} |
|
|
|
$this->warnings[QuotedPart::CODE] = |
|
new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']); |
|
return true; |
|
|
|
} |
|
|
|
/** |
|
* @param bool $hasClosingQuote |
|
* |
|
* @return bool |
|
*/ |
|
protected function checkDQUOTE($hasClosingQuote) |
|
{ |
|
if ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE) { |
|
return $hasClosingQuote; |
|
} |
|
if ($hasClosingQuote) { |
|
return $hasClosingQuote; |
|
} |
|
$previous = $this->lexer->getPrevious(); |
|
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] === EmailLexer::GENERIC) { |
|
throw new ExpectingATEXT(); |
|
} |
|
|
|
try { |
|
$this->lexer->find(EmailLexer::S_DQUOTE); |
|
$hasClosingQuote = true; |
|
} catch (\Exception $e) { |
|
throw new UnclosedQuotedString(); |
|
} |
|
$this->warnings[QuotedString::CODE] = new QuotedString($previous['value'], $this->lexer->token['value']); |
|
|
|
return $hasClosingQuote; |
|
} |
|
|
|
protected function checkCRLFInFWS() |
|
{ |
|
if ($this->lexer->token['type'] !== EmailLexer::CRLF) { |
|
return; |
|
} |
|
|
|
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) { |
|
throw new CRLFX2(); |
|
} |
|
|
|
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) { |
|
throw new CRLFAtTheEnd(); |
|
} |
|
} |
|
}
|
|
|