Chris@18
|
1 <?php
|
Chris@18
|
2
|
Chris@18
|
3 namespace Egulias\EmailValidator\Parser;
|
Chris@18
|
4
|
Chris@18
|
5 use Egulias\EmailValidator\EmailLexer;
|
Chris@18
|
6 use Egulias\EmailValidator\Exception\AtextAfterCFWS;
|
Chris@18
|
7 use Egulias\EmailValidator\Exception\ConsecutiveDot;
|
Chris@18
|
8 use Egulias\EmailValidator\Exception\CRLFAtTheEnd;
|
Chris@18
|
9 use Egulias\EmailValidator\Exception\CRLFX2;
|
Chris@18
|
10 use Egulias\EmailValidator\Exception\CRNoLF;
|
Chris@18
|
11 use Egulias\EmailValidator\Exception\ExpectedQPair;
|
Chris@18
|
12 use Egulias\EmailValidator\Exception\ExpectingATEXT;
|
Chris@18
|
13 use Egulias\EmailValidator\Exception\ExpectingCTEXT;
|
Chris@18
|
14 use Egulias\EmailValidator\Exception\UnclosedComment;
|
Chris@18
|
15 use Egulias\EmailValidator\Exception\UnclosedQuotedString;
|
Chris@18
|
16 use Egulias\EmailValidator\Warning\CFWSNearAt;
|
Chris@18
|
17 use Egulias\EmailValidator\Warning\CFWSWithFWS;
|
Chris@18
|
18 use Egulias\EmailValidator\Warning\Comment;
|
Chris@18
|
19 use Egulias\EmailValidator\Warning\QuotedPart;
|
Chris@18
|
20 use Egulias\EmailValidator\Warning\QuotedString;
|
Chris@18
|
21
|
Chris@18
|
22 abstract class Parser
|
Chris@18
|
23 {
|
Chris@18
|
24 protected $warnings = [];
|
Chris@18
|
25 protected $lexer;
|
Chris@18
|
26 protected $openedParenthesis = 0;
|
Chris@18
|
27
|
Chris@18
|
28 public function __construct(EmailLexer $lexer)
|
Chris@18
|
29 {
|
Chris@18
|
30 $this->lexer = $lexer;
|
Chris@18
|
31 }
|
Chris@18
|
32
|
Chris@18
|
33 public function getWarnings()
|
Chris@18
|
34 {
|
Chris@18
|
35 return $this->warnings;
|
Chris@18
|
36 }
|
Chris@18
|
37
|
Chris@18
|
38 abstract public function parse($str);
|
Chris@18
|
39
|
Chris@18
|
40 /** @return int */
|
Chris@18
|
41 public function getOpenedParenthesis()
|
Chris@18
|
42 {
|
Chris@18
|
43 return $this->openedParenthesis;
|
Chris@18
|
44 }
|
Chris@18
|
45
|
Chris@18
|
46 /**
|
Chris@18
|
47 * validateQuotedPair
|
Chris@18
|
48 */
|
Chris@18
|
49 protected function validateQuotedPair()
|
Chris@18
|
50 {
|
Chris@18
|
51 if (!($this->lexer->token['type'] === EmailLexer::INVALID
|
Chris@18
|
52 || $this->lexer->token['type'] === EmailLexer::C_DEL)) {
|
Chris@18
|
53 throw new ExpectedQPair();
|
Chris@18
|
54 }
|
Chris@18
|
55
|
Chris@18
|
56 $this->warnings[QuotedPart::CODE] =
|
Chris@18
|
57 new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']);
|
Chris@18
|
58 }
|
Chris@18
|
59
|
Chris@18
|
60 protected function parseComments()
|
Chris@18
|
61 {
|
Chris@18
|
62 $this->openedParenthesis = 1;
|
Chris@18
|
63 $this->isUnclosedComment();
|
Chris@18
|
64 $this->warnings[Comment::CODE] = new Comment();
|
Chris@18
|
65 while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
|
Chris@18
|
66 if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) {
|
Chris@18
|
67 $this->openedParenthesis++;
|
Chris@18
|
68 }
|
Chris@18
|
69 $this->warnEscaping();
|
Chris@18
|
70 $this->lexer->moveNext();
|
Chris@18
|
71 }
|
Chris@18
|
72
|
Chris@18
|
73 $this->lexer->moveNext();
|
Chris@18
|
74 if ($this->lexer->isNextTokenAny(array(EmailLexer::GENERIC, EmailLexer::S_EMPTY))) {
|
Chris@18
|
75 throw new ExpectingATEXT();
|
Chris@18
|
76 }
|
Chris@18
|
77
|
Chris@18
|
78 if ($this->lexer->isNextToken(EmailLexer::S_AT)) {
|
Chris@18
|
79 $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt();
|
Chris@18
|
80 }
|
Chris@18
|
81 }
|
Chris@18
|
82
|
Chris@18
|
83 protected function isUnclosedComment()
|
Chris@18
|
84 {
|
Chris@18
|
85 try {
|
Chris@18
|
86 $this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS);
|
Chris@18
|
87 return true;
|
Chris@18
|
88 } catch (\RuntimeException $e) {
|
Chris@18
|
89 throw new UnclosedComment();
|
Chris@18
|
90 }
|
Chris@18
|
91 }
|
Chris@18
|
92
|
Chris@18
|
93 protected function parseFWS()
|
Chris@18
|
94 {
|
Chris@18
|
95 $previous = $this->lexer->getPrevious();
|
Chris@18
|
96
|
Chris@18
|
97 $this->checkCRLFInFWS();
|
Chris@18
|
98
|
Chris@18
|
99 if ($this->lexer->token['type'] === EmailLexer::S_CR) {
|
Chris@18
|
100 throw new CRNoLF();
|
Chris@18
|
101 }
|
Chris@18
|
102
|
Chris@18
|
103 if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) {
|
Chris@18
|
104 throw new AtextAfterCFWS();
|
Chris@18
|
105 }
|
Chris@18
|
106
|
Chris@18
|
107 if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) {
|
Chris@18
|
108 throw new ExpectingCTEXT();
|
Chris@18
|
109 }
|
Chris@18
|
110
|
Chris@18
|
111 if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous['type'] === EmailLexer::S_AT) {
|
Chris@18
|
112 $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt();
|
Chris@18
|
113 } else {
|
Chris@18
|
114 $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
|
Chris@18
|
115 }
|
Chris@18
|
116 }
|
Chris@18
|
117
|
Chris@18
|
118 protected function checkConsecutiveDots()
|
Chris@18
|
119 {
|
Chris@18
|
120 if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
|
Chris@18
|
121 throw new ConsecutiveDot();
|
Chris@18
|
122 }
|
Chris@18
|
123 }
|
Chris@18
|
124
|
Chris@18
|
125 protected function isFWS()
|
Chris@18
|
126 {
|
Chris@18
|
127 if ($this->escaped()) {
|
Chris@18
|
128 return false;
|
Chris@18
|
129 }
|
Chris@18
|
130
|
Chris@18
|
131 if ($this->lexer->token['type'] === EmailLexer::S_SP ||
|
Chris@18
|
132 $this->lexer->token['type'] === EmailLexer::S_HTAB ||
|
Chris@18
|
133 $this->lexer->token['type'] === EmailLexer::S_CR ||
|
Chris@18
|
134 $this->lexer->token['type'] === EmailLexer::S_LF ||
|
Chris@18
|
135 $this->lexer->token['type'] === EmailLexer::CRLF
|
Chris@18
|
136 ) {
|
Chris@18
|
137 return true;
|
Chris@18
|
138 }
|
Chris@18
|
139
|
Chris@18
|
140 return false;
|
Chris@18
|
141 }
|
Chris@18
|
142
|
Chris@18
|
143 protected function escaped()
|
Chris@18
|
144 {
|
Chris@18
|
145 $previous = $this->lexer->getPrevious();
|
Chris@18
|
146
|
Chris@18
|
147 if ($previous['type'] === EmailLexer::S_BACKSLASH
|
Chris@18
|
148 &&
|
Chris@18
|
149 $this->lexer->token['type'] !== EmailLexer::GENERIC
|
Chris@18
|
150 ) {
|
Chris@18
|
151 return true;
|
Chris@18
|
152 }
|
Chris@18
|
153
|
Chris@18
|
154 return false;
|
Chris@18
|
155 }
|
Chris@18
|
156
|
Chris@18
|
157 protected function warnEscaping()
|
Chris@18
|
158 {
|
Chris@18
|
159 if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) {
|
Chris@18
|
160 return false;
|
Chris@18
|
161 }
|
Chris@18
|
162
|
Chris@18
|
163 if ($this->lexer->isNextToken(EmailLexer::GENERIC)) {
|
Chris@18
|
164 throw new ExpectingATEXT();
|
Chris@18
|
165 }
|
Chris@18
|
166
|
Chris@18
|
167 if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
|
Chris@18
|
168 return false;
|
Chris@18
|
169 }
|
Chris@18
|
170
|
Chris@18
|
171 $this->warnings[QuotedPart::CODE] =
|
Chris@18
|
172 new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']);
|
Chris@18
|
173 return true;
|
Chris@18
|
174
|
Chris@18
|
175 }
|
Chris@18
|
176
|
Chris@18
|
177 protected function checkDQUOTE($hasClosingQuote)
|
Chris@18
|
178 {
|
Chris@18
|
179 if ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE) {
|
Chris@18
|
180 return $hasClosingQuote;
|
Chris@18
|
181 }
|
Chris@18
|
182 if ($hasClosingQuote) {
|
Chris@18
|
183 return $hasClosingQuote;
|
Chris@18
|
184 }
|
Chris@18
|
185 $previous = $this->lexer->getPrevious();
|
Chris@18
|
186 if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] === EmailLexer::GENERIC) {
|
Chris@18
|
187 throw new ExpectingATEXT();
|
Chris@18
|
188 }
|
Chris@18
|
189
|
Chris@18
|
190 try {
|
Chris@18
|
191 $this->lexer->find(EmailLexer::S_DQUOTE);
|
Chris@18
|
192 $hasClosingQuote = true;
|
Chris@18
|
193 } catch (\Exception $e) {
|
Chris@18
|
194 throw new UnclosedQuotedString();
|
Chris@18
|
195 }
|
Chris@18
|
196 $this->warnings[QuotedString::CODE] = new QuotedString($previous['value'], $this->lexer->token['value']);
|
Chris@18
|
197
|
Chris@18
|
198 return $hasClosingQuote;
|
Chris@18
|
199 }
|
Chris@18
|
200
|
Chris@18
|
201 protected function checkCRLFInFWS()
|
Chris@18
|
202 {
|
Chris@18
|
203 if ($this->lexer->token['type'] !== EmailLexer::CRLF) {
|
Chris@18
|
204 return;
|
Chris@18
|
205 }
|
Chris@18
|
206
|
Chris@18
|
207 if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
|
Chris@18
|
208 throw new CRLFX2();
|
Chris@18
|
209 }
|
Chris@18
|
210
|
Chris@18
|
211 if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
|
Chris@18
|
212 throw new CRLFAtTheEnd();
|
Chris@18
|
213 }
|
Chris@18
|
214 }
|
Chris@18
|
215 }
|