1 | <?php |
||||
2 | |||||
3 | namespace Egulias\EmailValidator; |
||||
4 | |||||
5 | use Doctrine\Common\Lexer\AbstractLexer; |
||||
6 | use Doctrine\Common\Lexer\Token; |
||||
7 | |||||
8 | /** @extends AbstractLexer<int, string> */ |
||||
9 | class EmailLexer extends AbstractLexer |
||||
10 | { |
||||
11 | //ASCII values |
||||
12 | public const S_EMPTY = -1; |
||||
13 | public const C_NUL = 0; |
||||
14 | public const S_HTAB = 9; |
||||
15 | public const S_LF = 10; |
||||
16 | public const S_CR = 13; |
||||
17 | public const S_SP = 32; |
||||
18 | public const EXCLAMATION = 33; |
||||
19 | public const S_DQUOTE = 34; |
||||
20 | public const NUMBER_SIGN = 35; |
||||
21 | public const DOLLAR = 36; |
||||
22 | public const PERCENTAGE = 37; |
||||
23 | public const AMPERSAND = 38; |
||||
24 | public const S_SQUOTE = 39; |
||||
25 | public const S_OPENPARENTHESIS = 40; |
||||
26 | public const S_CLOSEPARENTHESIS = 41; |
||||
27 | public const ASTERISK = 42; |
||||
28 | public const S_PLUS = 43; |
||||
29 | public const S_COMMA = 44; |
||||
30 | public const S_HYPHEN = 45; |
||||
31 | public const S_DOT = 46; |
||||
32 | public const S_SLASH = 47; |
||||
33 | public const S_COLON = 58; |
||||
34 | public const S_SEMICOLON = 59; |
||||
35 | public const S_LOWERTHAN = 60; |
||||
36 | public const S_EQUAL = 61; |
||||
37 | public const S_GREATERTHAN = 62; |
||||
38 | public const QUESTIONMARK = 63; |
||||
39 | public const S_AT = 64; |
||||
40 | public const S_OPENBRACKET = 91; |
||||
41 | public const S_BACKSLASH = 92; |
||||
42 | public const S_CLOSEBRACKET = 93; |
||||
43 | public const CARET = 94; |
||||
44 | public const S_UNDERSCORE = 95; |
||||
45 | public const S_BACKTICK = 96; |
||||
46 | public const S_OPENCURLYBRACES = 123; |
||||
47 | public const S_PIPE = 124; |
||||
48 | public const S_CLOSECURLYBRACES = 125; |
||||
49 | public const S_TILDE = 126; |
||||
50 | public const C_DEL = 127; |
||||
51 | public const INVERT_QUESTIONMARK = 168; |
||||
52 | public const INVERT_EXCLAMATION = 173; |
||||
53 | public const GENERIC = 300; |
||||
54 | public const S_IPV6TAG = 301; |
||||
55 | public const INVALID = 302; |
||||
56 | public const CRLF = 1310; |
||||
57 | public const S_DOUBLECOLON = 5858; |
||||
58 | public const ASCII_INVALID_FROM = 127; |
||||
59 | public const ASCII_INVALID_TO = 199; |
||||
60 | |||||
61 | /** |
||||
62 | * US-ASCII visible characters not valid for atext (@link http://tools.ietf.org/html/rfc5322#section-3.2.3) |
||||
63 | * |
||||
64 | * @var array |
||||
65 | */ |
||||
66 | protected $charValue = [ |
||||
67 | '{' => self::S_OPENCURLYBRACES, |
||||
68 | '}' => self::S_CLOSECURLYBRACES, |
||||
69 | '(' => self::S_OPENPARENTHESIS, |
||||
70 | ')' => self::S_CLOSEPARENTHESIS, |
||||
71 | '<' => self::S_LOWERTHAN, |
||||
72 | '>' => self::S_GREATERTHAN, |
||||
73 | '[' => self::S_OPENBRACKET, |
||||
74 | ']' => self::S_CLOSEBRACKET, |
||||
75 | ':' => self::S_COLON, |
||||
76 | ';' => self::S_SEMICOLON, |
||||
77 | '@' => self::S_AT, |
||||
78 | '\\' => self::S_BACKSLASH, |
||||
79 | '/' => self::S_SLASH, |
||||
80 | ',' => self::S_COMMA, |
||||
81 | '.' => self::S_DOT, |
||||
82 | "'" => self::S_SQUOTE, |
||||
83 | "`" => self::S_BACKTICK, |
||||
84 | '"' => self::S_DQUOTE, |
||||
85 | '-' => self::S_HYPHEN, |
||||
86 | '::' => self::S_DOUBLECOLON, |
||||
87 | ' ' => self::S_SP, |
||||
88 | "\t" => self::S_HTAB, |
||||
89 | "\r" => self::S_CR, |
||||
90 | "\n" => self::S_LF, |
||||
91 | "\r\n" => self::CRLF, |
||||
92 | 'IPv6' => self::S_IPV6TAG, |
||||
93 | '' => self::S_EMPTY, |
||||
94 | '\0' => self::C_NUL, |
||||
95 | '*' => self::ASTERISK, |
||||
96 | '!' => self::EXCLAMATION, |
||||
97 | '&' => self::AMPERSAND, |
||||
98 | '^' => self::CARET, |
||||
99 | '$' => self::DOLLAR, |
||||
100 | '%' => self::PERCENTAGE, |
||||
101 | '~' => self::S_TILDE, |
||||
102 | '|' => self::S_PIPE, |
||||
103 | '_' => self::S_UNDERSCORE, |
||||
104 | '=' => self::S_EQUAL, |
||||
105 | '+' => self::S_PLUS, |
||||
106 | '¿' => self::INVERT_QUESTIONMARK, |
||||
107 | '?' => self::QUESTIONMARK, |
||||
108 | '#' => self::NUMBER_SIGN, |
||||
109 | '¡' => self::INVERT_EXCLAMATION, |
||||
110 | ]; |
||||
111 | |||||
112 | public const INVALID_CHARS_REGEX = "/[^\p{S}\p{C}\p{Cc}]+/iu"; |
||||
113 | |||||
114 | public const VALID_UTF8_REGEX = '/\p{Cc}+/u'; |
||||
115 | |||||
116 | public const CATCHABLE_PATTERNS = [ |
||||
117 | '[a-zA-Z]+[46]?', //ASCII and domain literal |
||||
118 | '[^\x00-\x7F]', //UTF-8 |
||||
119 | '[0-9]+', |
||||
120 | '\r\n', |
||||
121 | '::', |
||||
122 | '\s+?', |
||||
123 | '.', |
||||
124 | ]; |
||||
125 | |||||
126 | public const NON_CATCHABLE_PATTERNS = [ |
||||
127 | '[\xA0-\xff]+', |
||||
128 | ]; |
||||
129 | |||||
130 | public const MODIFIERS = 'iu'; |
||||
131 | |||||
132 | /** @var bool */ |
||||
133 | protected $hasInvalidTokens = false; |
||||
134 | |||||
135 | /** |
||||
136 | * @var Token<int, string> |
||||
137 | */ |
||||
138 | protected Token $previous; |
||||
139 | |||||
140 | /** |
||||
141 | * The last matched/seen token. |
||||
142 | * |
||||
143 | * @var Token<int, string> |
||||
144 | */ |
||||
145 | public Token $current; |
||||
146 | |||||
147 | /** |
||||
148 | * @var Token<int, string> |
||||
149 | */ |
||||
150 | private Token $nullToken; |
||||
151 | |||||
152 | /** @var string */ |
||||
153 | private $accumulator = ''; |
||||
154 | |||||
155 | /** @var bool */ |
||||
156 | private $hasToRecord = false; |
||||
157 | |||||
158 | public function __construct() |
||||
159 | { |
||||
160 | /** @var Token<int, string> $nullToken */ |
||||
161 | $nullToken = new Token('', self::S_EMPTY, 0); |
||||
162 | $this->nullToken = $nullToken; |
||||
163 | |||||
164 | $this->current = $this->previous = $this->nullToken; |
||||
165 | $this->lookahead = null; |
||||
166 | } |
||||
167 | |||||
168 | public function reset(): void |
||||
169 | { |
||||
170 | $this->hasInvalidTokens = false; |
||||
171 | parent::reset(); |
||||
172 | $this->current = $this->previous = $this->nullToken; |
||||
173 | } |
||||
174 | |||||
175 | /** |
||||
176 | * @param int $type |
||||
177 | 340 | * @throws \UnexpectedValueException |
|||
178 | * @return boolean |
||||
179 | 340 | * |
|||
180 | 340 | * @psalm-suppress InvalidScalarArgument |
|||
181 | */ |
||||
182 | public function find($type): bool |
||||
183 | 308 | { |
|||
184 | $search = clone $this; |
||||
185 | 308 | $search->skipUntil($type); |
|||
186 | 308 | ||||
187 | 308 | if (!$search->lookahead) { |
|||
188 | throw new \UnexpectedValueException($type . ' not found'); |
||||
189 | } |
||||
190 | return true; |
||||
191 | } |
||||
192 | |||||
193 | /** |
||||
194 | * moveNext |
||||
195 | * |
||||
196 | * @return boolean |
||||
197 | 54 | */ |
|||
198 | public function moveNext(): bool |
||||
199 | 54 | { |
|||
200 | 54 | if ($this->hasToRecord && $this->previous === $this->nullToken) { |
|||
201 | $this->accumulator .= $this->current->value; |
||||
202 | 54 | } |
|||
203 | 6 | ||||
204 | $this->previous = $this->current; |
||||
205 | 48 | ||||
206 | if ($this->lookahead === null) { |
||||
207 | $this->lookahead = $this->nullToken; |
||||
0 ignored issues
–
show
|
|||||
208 | } |
||||
209 | |||||
210 | $hasNext = parent::moveNext(); |
||||
211 | $this->current = $this->token ?? $this->nullToken; |
||||
212 | |||||
213 | 297 | if ($this->hasToRecord) { |
|||
214 | $this->accumulator .= $this->current->value; |
||||
215 | 297 | } |
|||
216 | 179 | ||||
217 | return $hasNext; |
||||
218 | } |
||||
219 | 297 | ||||
220 | 180 | /** |
|||
221 | 297 | * Retrieve token type. Also processes the token value if necessary. |
|||
222 | * |
||||
223 | 297 | * @param string $value |
|||
224 | 297 | * @throws \InvalidArgumentException |
|||
225 | * @return integer |
||||
226 | */ |
||||
227 | 297 | protected function getType(&$value): int |
|||
228 | { |
||||
229 | 297 | $encoded = $value; |
|||
230 | 179 | ||||
231 | if (mb_detect_encoding($value, 'auto', true) !== 'UTF-8') { |
||||
232 | $encoded = mb_convert_encoding($value, 'UTF-8', 'Windows-1252'); |
||||
233 | 297 | } |
|||
234 | |||||
235 | if ($this->isValid($encoded)) { |
||||
0 ignored issues
–
show
It seems like
$encoded can also be of type array ; however, parameter $value of Egulias\EmailValidator\EmailLexer::isValid() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
236 | return $this->charValue[$encoded]; |
||||
237 | } |
||||
238 | |||||
239 | if ($this->isNullType($encoded)) { |
||||
0 ignored issues
–
show
It seems like
$encoded can also be of type array ; however, parameter $value of Egulias\EmailValidator\EmailLexer::isNullType() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
240 | return self::C_NUL; |
||||
0 ignored issues
–
show
The expression
return self::C_NUL returns the type integer which is incompatible with the return type mandated by Doctrine\Common\Lexer\AbstractLexer::getType() of Doctrine\Common\Lexer\T|null .
In the issue above, the returned value is violating the contract defined by the mentioned interface. Let's take a look at an example: interface HasName {
/** @return string */
public function getName();
}
class Name {
public $name;
}
class User implements HasName {
/** @return string|Name */
public function getName() {
return new Name('foo'); // This is a violation of the ``HasName`` interface
// which only allows a string value to be returned.
}
}
![]() |
|||||
241 | } |
||||
242 | |||||
243 | 300 | if ($this->isInvalidChar($encoded)) { |
|||
0 ignored issues
–
show
It seems like
$encoded can also be of type array ; however, parameter $value of Egulias\EmailValidator\EmailLexer::isInvalidChar() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
244 | $this->hasInvalidTokens = true; |
||||
245 | 300 | return self::INVALID; |
|||
0 ignored issues
–
show
The expression
return self::INVALID returns the type integer which is incompatible with the return type mandated by Doctrine\Common\Lexer\AbstractLexer::getType() of Doctrine\Common\Lexer\T|null .
In the issue above, the returned value is violating the contract defined by the mentioned interface. Let's take a look at an example: interface HasName {
/** @return string */
public function getName();
}
class Name {
public $name;
}
class User implements HasName {
/** @return string|Name */
public function getName() {
return new Name('foo'); // This is a violation of the ``HasName`` interface
// which only allows a string value to be returned.
}
}
![]() |
|||||
246 | } |
||||
247 | 300 | ||||
248 | 237 | return self::GENERIC; |
|||
0 ignored issues
–
show
The expression
return self::GENERIC returns the type integer which is incompatible with the return type mandated by Doctrine\Common\Lexer\AbstractLexer::getType() of Doctrine\Common\Lexer\T|null .
In the issue above, the returned value is violating the contract defined by the mentioned interface. Let's take a look at an example: interface HasName {
/** @return string */
public function getName();
}
class Name {
public $name;
}
class User implements HasName {
/** @return string|Name */
public function getName() {
return new Name('foo'); // This is a violation of the ``HasName`` interface
// which only allows a string value to be returned.
}
}
![]() |
|||||
249 | } |
||||
250 | |||||
251 | 300 | protected function isValid(string $value): bool |
|||
252 | 229 | { |
|||
253 | return isset($this->charValue[$value]); |
||||
254 | } |
||||
255 | 262 | ||||
256 | 2 | protected function isNullType(string $value): bool |
|||
257 | { |
||||
258 | return $value === "\0"; |
||||
259 | 261 | } |
|||
260 | 66 | ||||
261 | 66 | protected function isInvalidChar(string $value): bool |
|||
262 | { |
||||
263 | return !preg_match(self::INVALID_CHARS_REGEX, $value); |
||||
264 | } |
||||
265 | 199 | ||||
266 | protected function isUTF8Invalid(string $value): bool |
||||
267 | { |
||||
268 | 300 | return preg_match(self::VALID_UTF8_REGEX, $value) !== false; |
|||
269 | } |
||||
270 | 300 | ||||
271 | public function hasInvalidTokens(): bool |
||||
272 | { |
||||
273 | 262 | return $this->hasInvalidTokens; |
|||
274 | } |
||||
275 | 262 | ||||
276 | /** |
||||
277 | * getPrevious |
||||
278 | 261 | * |
|||
279 | * @return Token<int, string> |
||||
280 | 261 | */ |
|||
281 | public function getPrevious(): Token |
||||
282 | { |
||||
283 | return $this->previous; |
||||
284 | } |
||||
285 | |||||
286 | /** |
||||
287 | * Lexical catchable patterns. |
||||
288 | 191 | * |
|||
289 | * @return string[] |
||||
290 | 191 | */ |
|||
291 | protected function getCatchablePatterns(): array |
||||
292 | { |
||||
293 | return self::CATCHABLE_PATTERNS; |
||||
294 | } |
||||
295 | |||||
296 | /** |
||||
297 | * Lexical non-catchable patterns. |
||||
298 | 178 | * |
|||
299 | * @return string[] |
||||
300 | 178 | */ |
|||
301 | protected function getNonCatchablePatterns(): array |
||||
302 | { |
||||
303 | return self::NON_CATCHABLE_PATTERNS; |
||||
304 | } |
||||
305 | |||||
306 | protected function getModifiers(): string |
||||
307 | { |
||||
308 | 301 | return self::MODIFIERS; |
|||
309 | } |
||||
310 | 301 | ||||
311 | public function getAccumulatedValues(): string |
||||
312 | { |
||||
313 | return $this->accumulator; |
||||
314 | } |
||||
315 | |||||
316 | public function startRecording(): void |
||||
317 | { |
||||
318 | 301 | $this->hasToRecord = true; |
|||
319 | } |
||||
320 | 301 | ||||
321 | public function stopRecording(): void |
||||
322 | { |
||||
323 | 301 | $this->hasToRecord = false; |
|||
324 | } |
||||
325 | 301 | ||||
326 | public function clearRecorded(): void |
||||
327 | { |
||||
328 | 151 | $this->accumulator = ''; |
|||
329 | } |
||||
330 | } |
||||
331 |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..