1<?php 2 3namespace Egulias\EmailValidator\Parser; 4 5use Egulias\EmailValidator\Exception\DotAtEnd; 6use Egulias\EmailValidator\Exception\DotAtStart; 7use Egulias\EmailValidator\EmailLexer; 8use Egulias\EmailValidator\Exception\ExpectingAT; 9use Egulias\EmailValidator\Exception\ExpectingATEXT; 10use Egulias\EmailValidator\Exception\UnclosedQuotedString; 11use Egulias\EmailValidator\Exception\UnopenedComment; 12use Egulias\EmailValidator\Warning\CFWSWithFWS; 13use Egulias\EmailValidator\Warning\LocalTooLong; 14 15class LocalPart extends Parser 16{ 17 public function parse($localPart) 18 { 19 $parseDQuote = true; 20 $closingQuote = false; 21 $openedParenthesis = 0; 22 $totalLength = 0; 23 24 while ($this->lexer->token['type'] !== EmailLexer::S_AT && null !== $this->lexer->token['type']) { 25 if ($this->lexer->token['type'] === EmailLexer::S_DOT && null === $this->lexer->getPrevious()['type']) { 26 throw new DotAtStart(); 27 } 28 29 $closingQuote = $this->checkDQUOTE($closingQuote); 30 if ($closingQuote && $parseDQuote) { 31 $parseDQuote = $this->parseDoubleQuote(); 32 } 33 34 if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { 35 $this->parseComments(); 36 $openedParenthesis += $this->getOpenedParenthesis(); 37 } 38 39 if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) { 40 if ($openedParenthesis === 0) { 41 throw new UnopenedComment(); 42 } 43 44 $openedParenthesis--; 45 } 46 47 $this->checkConsecutiveDots(); 48 49 if ($this->lexer->token['type'] === EmailLexer::S_DOT && 50 $this->lexer->isNextToken(EmailLexer::S_AT) 51 ) { 52 throw new DotAtEnd(); 53 } 54 55 $this->warnEscaping(); 56 $this->isInvalidToken($this->lexer->token, $closingQuote); 57 58 if ($this->isFWS()) { 59 $this->parseFWS(); 60 } 61 62 $totalLength += strlen($this->lexer->token['value']); 63 $this->lexer->moveNext(); 64 } 65 66 if ($totalLength > LocalTooLong::LOCAL_PART_LENGTH) { 67 $this->warnings[LocalTooLong::CODE] = new LocalTooLong(); 68 } 69 } 70 71 /** 72 * @return bool 73 */ 74 protected function parseDoubleQuote() 75 { 76 $parseAgain = true; 77 $special = array( 78 EmailLexer::S_CR => true, 79 EmailLexer::S_HTAB => true, 80 EmailLexer::S_LF => true 81 ); 82 83 $invalid = array( 84 EmailLexer::C_NUL => true, 85 EmailLexer::S_HTAB => true, 86 EmailLexer::S_CR => true, 87 EmailLexer::S_LF => true 88 ); 89 $setSpecialsWarning = true; 90 91 $this->lexer->moveNext(); 92 93 while ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE && null !== $this->lexer->token['type']) { 94 $parseAgain = false; 95 if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) { 96 $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); 97 $setSpecialsWarning = false; 98 } 99 if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH && $this->lexer->isNextToken(EmailLexer::S_DQUOTE)) { 100 $this->lexer->moveNext(); 101 } 102 103 $this->lexer->moveNext(); 104 105 if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) { 106 throw new ExpectingATEXT(); 107 } 108 } 109 110 $prev = $this->lexer->getPrevious(); 111 112 if ($prev['type'] === EmailLexer::S_BACKSLASH) { 113 if (!$this->checkDQUOTE(false)) { 114 throw new UnclosedQuotedString(); 115 } 116 } 117 118 if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) { 119 throw new ExpectingAT(); 120 } 121 122 return $parseAgain; 123 } 124 125 /** 126 * @param bool $closingQuote 127 */ 128 protected function isInvalidToken(array $token, $closingQuote) 129 { 130 $forbidden = array( 131 EmailLexer::S_COMMA, 132 EmailLexer::S_CLOSEBRACKET, 133 EmailLexer::S_OPENBRACKET, 134 EmailLexer::S_GREATERTHAN, 135 EmailLexer::S_LOWERTHAN, 136 EmailLexer::S_COLON, 137 EmailLexer::S_SEMICOLON, 138 EmailLexer::INVALID 139 ); 140 141 if (in_array($token['type'], $forbidden) && !$closingQuote) { 142 throw new ExpectingATEXT(); 143 } 144 } 145} 146