Test Setup Failed
Push — 3.0.0-dev ( a7a691...3dc66a )
by Eduardo Gulias
01:52
created

EmailLexer   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 354
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 1

Importance

Changes 0
Metric Value
wmc 31
lcom 2
cbo 1
dl 0
loc 354
rs 9.92
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A reset() 0 6 1
A hasInvalidTokens() 0 4 1
A find() 0 10 2
A getPrevious() 0 4 1
A moveNext() 0 16 5
A getCatchablePatterns() 0 12 1
A getNonCatchablePatterns() 0 6 1
A getType() 0 24 5
A isInvalidChar() 0 7 2
A isValid() 0 8 2
A isNullType() 0 8 2
A isUTF8Invalid() 0 8 2
A getModifiers() 0 4 1
A getAccumulatedValues() 0 4 1
A startRecording() 0 4 1
A stopRecording() 0 4 1
A clearRecorded() 0 4 1
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
        null   => self::S_EMPTY,
109
    );
110
111
    /**
112
     * @var bool
113
     */
114
    protected $hasInvalidTokens = false;
115
116
    /**
117
     * @var array
118
     *
119
     * @psalm-var array{value:string, type:null|int, position:int}|array<empty, empty>
120
     */
121
    protected $previous = [];
122
123
    /**
124
     * The last matched/seen token.
125
     *
126
     * @var array
127
     *
128
     * @psalm-var array{value:string, type:null|int, position:int}
129
     */
130
    public $token;
131
132
    /**
133
     * The next token in the input.
134
     *
135
     * @var array|null
136
     */
137
    public $lookahead;
138
139
    /**
140
     * @psalm-var array{value:'', type:null, position:0}
141
     */
142
    private static $nullToken = [
143
        'value' => '',
144
        'type' => null,
145
        'position' => 0,
146
    ];
147
148
    /**
149
     * @var string
150
     */
151
    private $accumulator = '';
152
153
    /**
154
     * @var bool
155
     */
156
    private $hasToRecord = false;
157
158
    public function __construct()
159
    {
160
        $this->previous = $this->token = self::$nullToken;
161
        $this->lookahead = null;
162
    }
163
164
    /**
165
     * @return void
166
     */
167
    public function reset()
168
    {
169
        $this->hasInvalidTokens = false;
170
        parent::reset();
171
        $this->previous = $this->token = self::$nullToken;
172
    }
173
174
    /**
175
     * @return bool
176
     */
177
    public function hasInvalidTokens()
178
    {
179
        return $this->hasInvalidTokens;
180
    }
181
182
    /**
183
     * @param int $type
184
     * @throws \UnexpectedValueException
185
     * @return boolean
186
     *
187
     * @psalm-suppress InvalidScalarArgument
188
     */
189
    public function find($type)
190
    {
191
        $search = clone $this;
192
        $search->skipUntil($type);
193
194
        if (!$search->lookahead) {
195
            throw new \UnexpectedValueException($type . ' not found');
196
        }
197
        return true;
198
    }
199
200
    /**
201
     * getPrevious
202
     *
203
     * @return array
204
     */
205
    public function getPrevious()
206
    {
207
        return $this->previous;
208
    }
209
210
    /**
211
     * moveNext
212
     *
213
     * @return boolean
214
     */
215
    public function moveNext()
216
    {
217
        if ($this->hasToRecord && $this->previous === self::$nullToken) {
218
            $this->accumulator .= $this->token['value'];
219
        }
220
221
        $this->previous = $this->token;
222
        $hasNext = parent::moveNext();
223
        $this->token = $this->token ?: self::$nullToken;
224
225
        if ($this->hasToRecord) {
226
            $this->accumulator .= $this->token['value'];
227
        }
228
229
        return $hasNext;
230
    }
231
232
    /**
233
     * Lexical catchable patterns.
234
     *
235
     * @return string[]
236
     */
237
    protected function getCatchablePatterns()
238
    {
239
        return array(
240
            '[a-zA-Z]+[46]?', //ASCII and domain literal
241
            '[^\x00-\x7F]',  //UTF-8
242
            '[0-9]+',
243
            '\r\n',
244
            '::',
245
            '\s+?',
246
            '.',
247
            );
248
    }
249
250
    /**
251
     * Lexical non-catchable patterns.
252
     *
253
     * @return string[]
254
     */
255
    protected function getNonCatchablePatterns()
256
    {
257
        return [
258
            '[\xA0-\xff]+',
259
        ];
260
    }
261
262
    /**
263
     * Retrieve token type. Also processes the token value if necessary.
264
     *
265
     * @param string $value
266
     * @throws \InvalidArgumentException
267
     * @return integer
268
     */
269
    protected function getType(&$value)
270
    {
271
        $encoded = $value;
272
273
        if (mb_detect_encoding($value, 'auto', true) !== 'UTF-8') {
274
            $encoded = utf8_encode($value);
275
        }
276
277
        if ($this->isValid($encoded)) {
278
            return $this->charValue[$encoded];
279
        }
280
281
        if ($this->isNullType($encoded)) {
282
            return self::C_NUL;
283
        }
284
285
        if ($this->isInvalidChar($encoded)) {
286
            $this->hasInvalidTokens = true;
287
            return self::INVALID;
288
        }
289
290
291
        return  self::GENERIC;
292
    }
293
294
    protected function isInvalidChar(string $value) : bool
295
    {
296
        if(preg_match("/[^\p{S}\p{C}\p{Cc}]+/iu", $value) ) {
297
            return false;
298
        }
299
        return true;
300
    }
301
302
    protected function isValid(string $value) : bool
303
    {
304
        if (isset($this->charValue[$value])) {
305
            return true;
306
        }
307
308
        return false;
309
    }
310
311
    /**
312
     * @param string $value
313
     * @return bool
314
     */
315
    protected function isNullType($value)
316
    {
317
        if ($value === "\0") {
318
            return true;
319
        }
320
321
        return false;
322
    }
323
324
    protected function isUTF8Invalid(string $value) : bool
325
    {
326
        if (preg_match('/\p{Cc}+/u', $value)) {
327
            return true;
328
        }
329
330
        return false;
331
    }
332
333
    /**
334
     * @return string
335
     */
336
    protected function getModifiers()
337
    {
338
        return 'iu';
339
    }
340
341
    public function getAccumulatedValues() : string
342
    {
343
        return $this->accumulator;
344
    }
345
346
    public function startRecording() : void
347
    {
348
        $this->hasToRecord = true;
349
    }
350
351
    public function stopRecording() : void
352
    {
353
        $this->hasToRecord = false;
354
    }
355
356
    public function clearRecorded() : void
357
    {
358
        $this->accumulator = '';
359
    }
360
}
361