1<?php 2 3namespace Egulias\EmailValidator\Parser; 4 5use Egulias\EmailValidator\EmailLexer; 6use Egulias\EmailValidator\Result\Result; 7use Egulias\EmailValidator\Result\ValidEmail; 8use Egulias\EmailValidator\Result\InvalidEmail; 9use Egulias\EmailValidator\Warning\LocalTooLong; 10use Egulias\EmailValidator\Result\Reason\DotAtEnd; 11use Egulias\EmailValidator\Result\Reason\DotAtStart; 12use Egulias\EmailValidator\Result\Reason\ConsecutiveDot; 13use Egulias\EmailValidator\Result\Reason\ExpectingATEXT; 14use Egulias\EmailValidator\Parser\CommentStrategy\LocalComment; 15 16class LocalPart extends PartParser 17{ 18 /** 19 * @var string 20 */ 21 private $localPart = ''; 22 23 24 public function parse() : Result 25 { 26 $this->lexer->startRecording(); 27 28 while ($this->lexer->token['type'] !== EmailLexer::S_AT && null !== $this->lexer->token['type']) { 29 if ($this->hasDotAtStart()) { 30 return new InvalidEmail(new DotAtStart(), $this->lexer->token['value']); 31 } 32 33 if ($this->lexer->token['type'] === EmailLexer::S_DQUOTE) { 34 $dquoteParsingResult = $this->parseDoubleQuote(); 35 36 //Invalid double quote parsing 37 if($dquoteParsingResult->isInvalid()) { 38 return $dquoteParsingResult; 39 } 40 } 41 42 if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS || 43 $this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS ) { 44 $commentsResult = $this->parseComments(); 45 46 //Invalid comment parsing 47 if($commentsResult->isInvalid()) { 48 return $commentsResult; 49 } 50 } 51 52 if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) { 53 return new InvalidEmail(new ConsecutiveDot(), $this->lexer->token['value']); 54 } 55 56 if ($this->lexer->token['type'] === EmailLexer::S_DOT && 57 $this->lexer->isNextToken(EmailLexer::S_AT) 58 ) { 59 return new InvalidEmail(new DotAtEnd(), $this->lexer->token['value']); 60 } 61 62 $resultEscaping = $this->validateEscaping(); 63 if ($resultEscaping->isInvalid()) { 64 return $resultEscaping; 65 } 66 67 $resultToken = $this->validateTokens(false); 68 if ($resultToken->isInvalid()) { 69 return $resultToken; 70 } 71 72 $resultFWS = $this->parseLocalFWS(); 73 if($resultFWS->isInvalid()) { 74 return $resultFWS; 75 } 76 77 $this->lexer->moveNext(); 78 } 79 80 $this->lexer->stopRecording(); 81 $this->localPart = rtrim($this->lexer->getAccumulatedValues(), '@'); 82 if (strlen($this->localPart) > LocalTooLong::LOCAL_PART_LENGTH) { 83 $this->warnings[LocalTooLong::CODE] = new LocalTooLong(); 84 } 85 86 return new ValidEmail(); 87 } 88 89 protected function validateTokens(bool $hasComments) : Result 90 { 91 $invalidTokens = array( 92 EmailLexer::S_COMMA => EmailLexer::S_COMMA, 93 EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET, 94 EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET, 95 EmailLexer::S_GREATERTHAN => EmailLexer::S_GREATERTHAN, 96 EmailLexer::S_LOWERTHAN => EmailLexer::S_LOWERTHAN, 97 EmailLexer::S_COLON => EmailLexer::S_COLON, 98 EmailLexer::S_SEMICOLON => EmailLexer::S_SEMICOLON, 99 EmailLexer::INVALID => EmailLexer::INVALID 100 ); 101 if (isset($invalidTokens[$this->lexer->token['type']])) { 102 return new InvalidEmail(new ExpectingATEXT('Invalid token found'), $this->lexer->token['value']); 103 } 104 return new ValidEmail(); 105 } 106 107 public function localPart() : string 108 { 109 return $this->localPart; 110 } 111 112 private function parseLocalFWS() : Result 113 { 114 $foldingWS = new FoldingWhiteSpace($this->lexer); 115 $resultFWS = $foldingWS->parse(); 116 if ($resultFWS->isValid()) { 117 $this->warnings = array_merge($this->warnings, $foldingWS->getWarnings()); 118 } 119 return $resultFWS; 120 } 121 122 private function hasDotAtStart() : bool 123 { 124 return $this->lexer->token['type'] === EmailLexer::S_DOT && null === $this->lexer->getPrevious()['type']; 125 } 126 127 private function parseDoubleQuote() : Result 128 { 129 $dquoteParser = new DoubleQuote($this->lexer); 130 $parseAgain = $dquoteParser->parse(); 131 $this->warnings = array_merge($this->warnings, $dquoteParser->getWarnings()); 132 133 return $parseAgain; 134 } 135 136 protected function parseComments(): Result 137 { 138 $commentParser = new Comment($this->lexer, new LocalComment()); 139 $result = $commentParser->parse(); 140 $this->warnings = array_merge($this->warnings, $commentParser->getWarnings()); 141 if($result->isInvalid()) { 142 return $result; 143 } 144 return $result; 145 } 146 147 private function validateEscaping() : Result 148 { 149 //Backslash found 150 if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) { 151 return new ValidEmail(); 152 } 153 154 if ($this->lexer->isNextToken(EmailLexer::GENERIC)) { 155 return new InvalidEmail(new ExpectingATEXT('Found ATOM after escaping'), $this->lexer->token['value']); 156 } 157 158 if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) { 159 return new ValidEmail(); 160 } 161 162 return new ValidEmail(); 163 } 164}