Passed
Push — 3.x ( 4cccb8...037594 )
by Eduardo Gulias
02:37
created

EmailLexer::isInvalidChar()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Egulias\EmailValidator;
4
5
use Doctrine\Common\Lexer\AbstractLexer;
6
7
class EmailLexer extends AbstractLexer
8
{
9
    //ASCII values
10
    const S_EMPTY            = null;
11
    const C_NUL              = 0;
12
    const S_HTAB             = 9;
13
    const S_LF               = 10;
14
    const S_CR               = 13;
15
    const S_SP               = 32;
16
    const EXCLAMATION        = 33;
17
    const S_DQUOTE           = 34;
18
    const NUMBER_SIGN        = 35;
19
    const DOLLAR             = 36;
20
    const PERCENTAGE         = 37;
21
    const AMPERSAND          = 38;
22
    const S_SQUOTE           = 39;
23
    const S_OPENPARENTHESIS  = 40;
24
    const S_CLOSEPARENTHESIS = 41;
25
    const ASTERISK           = 42;
26
    const S_PLUS             = 43;
27
    const S_COMMA            = 44;
28
    const S_HYPHEN           = 45;
29
    const S_DOT              = 46;
30
    const S_SLASH            = 47;
31
    const S_COLON            = 58;
32
    const S_SEMICOLON        = 59;
33
    const S_LOWERTHAN        = 60;
34
    const S_EQUAL            = 61;
35
    const S_GREATERTHAN      = 62;
36
    const QUESTIONMARK       = 63;
37
    const S_AT               = 64;
38
    const S_OPENBRACKET      = 91;
39
    const S_BACKSLASH        = 92;
40
    const S_CLOSEBRACKET     = 93;
41
    const CARET              = 94;
42
    const S_UNDERSCORE       = 95;
43
    const S_BACKTICK         = 96;
44
    const S_OPENCURLYBRACES  = 123;
45
    const S_PIPE             = 124;
46
    const S_CLOSECURLYBRACES = 125;
47
    const S_TILDE            = 126;
48
    const C_DEL              = 127;
49
    const INVERT_QUESTIONMARK= 168;
50
    const INVERT_EXCLAMATION = 173;
51
    const GENERIC            = 300;
52
    const S_IPV6TAG          = 301;
53
    const INVALID            = 302;
54
    const CRLF               = 1310;
55
    const S_DOUBLECOLON      = 5858;
56
    const ASCII_INVALID_FROM = 127;
57
    const ASCII_INVALID_TO   = 199;
58
59
    /**
60
     * US-ASCII visible characters not valid for atext (@link http://tools.ietf.org/html/rfc5322#section-3.2.3)
61
     *
62
     * @var array
63
     */
64
    protected $charValue = array(
65
        '{'    => self::S_OPENCURLYBRACES,
66
        '}'    => self::S_CLOSECURLYBRACES,
67
        '('    => self::S_OPENPARENTHESIS,
68
        ')'    => self::S_CLOSEPARENTHESIS,
69
        '<'    => self::S_LOWERTHAN,
70
        '>'    => self::S_GREATERTHAN,
71
        '['    => self::S_OPENBRACKET,
72
        ']'    => self::S_CLOSEBRACKET,
73
        ':'    => self::S_COLON,
74
        ';'    => self::S_SEMICOLON,
75
        '@'    => self::S_AT,
76
        '\\'   => self::S_BACKSLASH,
77
        '/'    => self::S_SLASH,
78
        ','    => self::S_COMMA,
79
        '.'    => self::S_DOT,
80
        "'"    => self::S_SQUOTE,
81
        "`"    => self::S_BACKTICK,
82
        '"'    => self::S_DQUOTE,
83
        '-'    => self::S_HYPHEN,
84
        '::'   => self::S_DOUBLECOLON,
85
        ' '    => self::S_SP,
86
        "\t"   => self::S_HTAB,
87
        "\r"   => self::S_CR,
88
        "\n"   => self::S_LF,
89
        "\r\n" => self::CRLF,
90
        'IPv6' => self::S_IPV6TAG,
91
        ''     => self::S_EMPTY,
92
        '\0'   => self::C_NUL,
93
        '*'    => self::ASTERISK,
94
        '!'    => self::EXCLAMATION,
95
        '&'    => self::AMPERSAND,
96
        '^'    => self::CARET,
97
        '$'    => self::DOLLAR,
98
        '%'    => self::PERCENTAGE,
99
        '~'    => self::S_TILDE,
100
        '|'    => self::S_PIPE,
101
        '_'    => self::S_UNDERSCORE,
102
        '='    => self::S_EQUAL,
103
        '+'    => self::S_PLUS,
104
        '¿'    => self::INVERT_QUESTIONMARK,
105
        '?'    => self::QUESTIONMARK,
106
        '#'    => self::NUMBER_SIGN,
107
        '¡'    => self::INVERT_EXCLAMATION,
108
    );
109
110
    /**
111
     * @var bool
112
     */
113
    protected $hasInvalidTokens = false;
114
115
    /**
116
     * @var array
117
     *
118
     * @psalm-var array{value:string, type:null|int, position:int}|array<empty, empty>
119
     */
120
    protected $previous = [];
121
122
    /**
123
     * The last matched/seen token.
124
     *
125
     * @var array
126
     *
127
     * @psalm-suppress NonInvariantDocblockPropertyType
128
     * @psalm-var array{value:string, type:null|int, position:int}
129
     * @psalm-suppress NonInvariantDocblockPropertyType
130
     */
131
    public $token;
132
133
    /**
134
     * The next token in the input.
135
     *
136
     * @var array|null
137
     */
138
    public $lookahead;
139
140
    /**
141
     * @psalm-var array{value:'', type:null, position:0}
142
     */
143
    private static $nullToken = [
144
        'value' => '',
145
        'type' => null,
146
        'position' => 0,
147
    ];
148
149
    /**
150
     * @var string
151
     */
152
    private $accumulator = '';
153
154
    /**
155
     * @var bool
156
     */
157
    private $hasToRecord = false;
158
159 339
    public function __construct()
160
    {
161 339
        $this->previous = $this->token = self::$nullToken;
162 339
        $this->lookahead = null;
163 339
    }
164
165
    /**
166
     * @return void
167
     */
168 308
    public function reset()
169
    {
170 308
        $this->hasInvalidTokens = false;
171 308
        parent::reset();
172 308
        $this->previous = $this->token = self::$nullToken;
173 308
    }
174
175
    /**
176
     * @return bool
177
     */
178 191
    public function hasInvalidTokens()
179
    {
180 191
        return $this->hasInvalidTokens;
181
    }
182
183
    /**
184
     * @param int $type
185
     * @throws \UnexpectedValueException
186
     * @return boolean
187
     *
188
     * @psalm-suppress InvalidScalarArgument
189
     */
190 54
    public function find($type)
191
    {
192 54
        $search = clone $this;
193 54
        $search->skipUntil($type);
194
195 54
        if (!$search->lookahead) {
196 6
            throw new \UnexpectedValueException($type . ' not found');
197
        }
198 48
        return true;
199
    }
200
201
    /**
202
     * getPrevious
203
     *
204
     * @return array
205
     */
206 178
    public function getPrevious()
207
    {
208 178
        return $this->previous;
209
    }
210
211
    /**
212
     * moveNext
213
     *
214
     * @return boolean
215
     */
216 297
    public function moveNext()
217
    {
218 297
        if ($this->hasToRecord && $this->previous === self::$nullToken) {
219 179
            $this->accumulator .= $this->token['value'];
220
        }
221
222 297
        $this->previous = $this->token;
223 297
        $hasNext = parent::moveNext();
224 297
        $this->token = $this->token ?: self::$nullToken;
225
226 297
        if ($this->hasToRecord) {
227 179
            $this->accumulator .= $this->token['value'];
228
        }
229
230 297
        return $hasNext;
231
    }
232
233
    /**
234
     * Lexical catchable patterns.
235
     *
236
     * @return string[]
237
     */
238 301
    protected function getCatchablePatterns()
239
    {
240
        return array(
241 301
            '[a-zA-Z]+[46]?', //ASCII and domain literal
242
            '[^\x00-\x7F]',  //UTF-8
243
            '[0-9]+',
244
            '\r\n',
245
            '::',
246
            '\s+?',
247
            '.',
248
            );
249
    }
250
251
    /**
252
     * Lexical non-catchable patterns.
253
     *
254
     * @return string[]
255
     */
256 301
    protected function getNonCatchablePatterns()
257
    {
258
        return [
259 301
            '[\xA0-\xff]+',
260
        ];
261
    }
262
263
    /**
264
     * Retrieve token type. Also processes the token value if necessary.
265
     *
266
     * @param string $value
267
     * @throws \InvalidArgumentException
268
     * @return integer
269
     */
270 300
    protected function getType(&$value)
271
    {
272 300
        $encoded = $value;
273
274 300
        if (mb_detect_encoding($value, 'auto', true) !== 'UTF-8') {
275 237
            $encoded = utf8_encode($value);
276
        }
277
278 300
        if ($this->isValid($encoded)) {
279 229
            return $this->charValue[$encoded];
280
        }
281
282 262
        if ($this->isNullType($encoded)) {
283 2
            return self::C_NUL;
284
        }
285
286 261
        if ($this->isInvalidChar($encoded)) {
287 66
            $this->hasInvalidTokens = true;
288 66
            return self::INVALID;
289
        }
290
291
292 199
        return  self::GENERIC;
293
    }
294
295 261
    protected function isInvalidChar(string $value) : bool
296
    {
297 261
        if(preg_match("/[^\p{S}\p{C}\p{Cc}]+/iu", $value) ) {
298 199
            return false;
299
        }
300 66
        return true;
301
    }
302
303 300
    protected function isValid(string $value) : bool
304
    {
305 300
        if (isset($this->charValue[$value])) {
306 229
            return true;
307
        }
308
309 262
        return false;
310
    }
311
312
    /**
313
     * @param string $value
314
     * @return bool
315
     */
316 262
    protected function isNullType($value)
317
    {
318 262
        if ($value === "\0") {
319 2
            return true;
320
        }
321
322 261
        return false;
323
    }
324
325
    protected function isUTF8Invalid(string $value) : bool
326
    {
327
        if (preg_match('/\p{Cc}+/u', $value)) {
328
            return true;
329
        }
330
331
        return false;
332
    }
333
334
    /**
335
     * @return string
336
     */
337 301
    protected function getModifiers()
338
    {
339 301
        return 'iu';
340
    }
341
342 151
    public function getAccumulatedValues() : string
343
    {
344 151
        return $this->accumulator;
345
    }
346
347 188
    public function startRecording() : void
348
    {
349 188
        $this->hasToRecord = true;
350 188
    }
351
352 148
    public function stopRecording() : void
353
    {
354 148
        $this->hasToRecord = false;
355 148
    }
356
357 149
    public function clearRecorded() : void
358
    {
359 149
        $this->accumulator = '';
360 149
    }
361
}
362