egulias /
EmailValidator
| 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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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.
}
}
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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.
}
}
Loading history...
|
|||||
| 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.
}
}
Loading history...
|
|||||
| 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..