Test Setup Failed
Push — 3.0.0-dev ( 61798f...c8e7f0 )
by Eduardo Gulias
01:58
created

DomainPart::parseComments()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 11
Ratio 100 %

Importance

Changes 0
Metric Value
dl 11
loc 11
rs 9.9
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace Egulias\EmailValidator\Parser;
4
5
use Egulias\EmailValidator\EmailLexer;
6
use Egulias\EmailValidator\Exception\CharNotAllowed;
7
use Egulias\EmailValidator\Exception\CommaInDomain;
8
use Egulias\EmailValidator\Exception\ConsecutiveAt;
9
use Egulias\EmailValidator\Exception\CRLFAtTheEnd;
10
use Egulias\EmailValidator\Exception\CRNoLF;
11
use Egulias\EmailValidator\Exception\DomainHyphened;
12
use Egulias\EmailValidator\Exception\DotAtEnd;
13
use Egulias\EmailValidator\Exception\ExpectingATEXT;
14
use Egulias\EmailValidator\Exception\ExpectingDomainLiteralClose;
15
use Egulias\EmailValidator\Exception\ExpectingDTEXT;
16
use Egulias\EmailValidator\Result\InvalidEmail;
17
use Egulias\EmailValidator\Result\Reason\DomainHyphened as ReasonDomainHyphened;
18
use Egulias\EmailValidator\Result\Reason\DotAtStart;
19
use Egulias\EmailValidator\Result\Reason\NoDomainPart as ReasonNoDomainPart;
20
use Egulias\EmailValidator\Result\Result;
21
use Egulias\EmailValidator\Result\ValidEmail;
22
use Egulias\EmailValidator\Warning\AddressLiteral;
23
use Egulias\EmailValidator\Warning\CFWSWithFWS;
24
use Egulias\EmailValidator\Warning\DeprecatedComment;
25
use Egulias\EmailValidator\Warning\DomainLiteral;
26
use Egulias\EmailValidator\Warning\DomainTooLong;
27
use Egulias\EmailValidator\Warning\IPV6BadChar;
28
use Egulias\EmailValidator\Warning\IPV6ColonEnd;
29
use Egulias\EmailValidator\Warning\IPV6ColonStart;
30
use Egulias\EmailValidator\Warning\IPV6Deprecated;
31
use Egulias\EmailValidator\Warning\IPV6DoubleColon;
32
use Egulias\EmailValidator\Warning\IPV6GroupCount;
33
use Egulias\EmailValidator\Warning\IPV6MaxGroups;
34
use Egulias\EmailValidator\Warning\LabelTooLong;
35
use Egulias\EmailValidator\Warning\ObsoleteDTEXT;
36
use Egulias\EmailValidator\Warning\TLD;
37
38
class DomainPart extends Parser
39
{
40
    const DOMAIN_MAX_LENGTH = 254;
41
42
    /**
43
     * @var string
44
     */
45
    protected $domainPart = '';
46
47
    public function parse($domainPart)
48
    {
49
        $this->lexer->moveNext();
50
51
        $domainChecks = $this->performDomainStartChecks();
52
        if ($domainChecks->isInvalid()) {
53
            return $domainChecks;
54
        }
55
56
        $domain = $this->doParseDomainPart();
57
        if ($domain->isInvalid()) {
58
            return $domain;
59
        }
60
61
        $prev = $this->lexer->getPrevious();
62
        $length = strlen($this->domainPart);
63
64
        if ($prev['type'] === EmailLexer::S_DOT) {
65
            throw new DotAtEnd();
66
        }
67
        if ($prev['type'] === EmailLexer::S_HYPHEN) {
68
            throw new DomainHyphened();
69
        }
70
        if ($length > self::DOMAIN_MAX_LENGTH) {
71
            $this->warnings[DomainTooLong::CODE] = new DomainTooLong();
72
        }
73
        if ($prev['type'] === EmailLexer::S_CR) {
74
            throw new CRLFAtTheEnd();
75
        }
76
77
        return new ValidEmail();
78
    }
79
80
    private function performDomainStartChecks() : Result
81
    {
82
        $invalidTokens = $this->checkInvalidTokensAfterAT();
83
        if ($invalidTokens->isInvalid()) {
84
            return $invalidTokens;
85
        }
86
        
87
        $missingDomain = $this->checkEmptyDomain();
88
        if ($missingDomain->isInvalid()) {
89
            return $missingDomain;
90
        }
91
92
        if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
93
            $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
94
        }
95
        return new ValidEmail();
96
    }
97
98
    private function checkEmptyDomain() : Result
99
    {
100
        $thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY ||
101
            ($this->lexer->token['type'] === EmailLexer::S_SP &&
102
            !$this->lexer->isNextToken(EmailLexer::GENERIC));
103
104
        if ($thereIsNoDomain) {
105
            return new InvalidEmail(new ReasonNoDomainPart(), $this->lexer->token['value']);
106
        }
107
108
        return new ValidEmail();
109
    }
110
111
    private function checkInvalidTokensAfterAT() : Result
112
    {
113 View Code Duplication
        if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
114
            return new InvalidEmail(new DotAtStart(), $this->lexer->token['value']);
115
        }
116 View Code Duplication
        if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
117
            return new InvalidEmail(new ReasonDomainHyphened('After AT'), $this->lexer->token['value']);
118
        }
119
        return new ValidEmail();
120
    }
121
122
    /**
123
     * @return string
124
     */
125
    public function getDomainPart()
126
    {
127
        return $this->domainPart;
128
    }
129
130
    /**
131
     * @param string $addressLiteral
132
     * @param int $maxGroups
133
     */
134
    public function checkIPV6Tag($addressLiteral, $maxGroups = 8)
135
    {
136
        $prev = $this->lexer->getPrevious();
137
        if ($prev['type'] === EmailLexer::S_COLON) {
138
            $this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd();
139
        }
140
141
        $IPv6       = substr($addressLiteral, 5);
142
        //Daniel Marschall's new IPv6 testing strategy
143
        $matchesIP  = explode(':', $IPv6);
144
        $groupCount = count($matchesIP);
145
        $colons     = strpos($IPv6, '::');
146
147
        if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) {
148
            $this->warnings[IPV6BadChar::CODE] = new IPV6BadChar();
149
        }
150
151
        if ($colons === false) {
152
            // We need exactly the right number of groups
153
            if ($groupCount !== $maxGroups) {
154
                $this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount();
155
            }
156
            return;
157
        }
158
159
        if ($colons !== strrpos($IPv6, '::')) {
160
            $this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon();
161
            return;
162
        }
163
164
        if ($colons === 0 || $colons === (strlen($IPv6) - 2)) {
165
            // RFC 4291 allows :: at the start or end of an address
166
            //with 7 other groups in addition
167
            ++$maxGroups;
168
        }
169
170
        if ($groupCount > $maxGroups) {
171
            $this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups();
172
        } elseif ($groupCount === $maxGroups) {
173
            $this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated();
174
        }
175
    }
176
177 View Code Duplication
    protected function parseComments()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
178
    {
179
        $commentParser = new Comment($this->lexer, new DomainComment());
180
        $result = $commentParser->parse('remove');
181
        if($result->isInvalid()) {
182
            return $result;
183
        }
184
185
        $this->warnings = array_merge($this->warnings, $commentParser->getWarnings());
186
        return $result;
187
    }
188
189
    protected function doParseDomainPart() : Result
190
    {
191
        $domain = '';
192
        do {
193
            $prev = $this->lexer->getPrevious();
194
195
            $this->checkNotAllowedChars($this->lexer->token);
196
197 View Code Duplication
            if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS || 
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
198
                $this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS ) {
199
                $commentsResult = $this->parseComments();
200
201
                //Invalid comment parsing
202
                if($commentsResult->isInvalid()) {
203
                    return $commentsResult;
204
                }
205
            }
206
207
            $this->checkConsecutiveDots();
208
            $this->checkDomainPartExceptions($prev);
209
210
            if ($this->hasBrackets()) {
211
                $this->parseDomainLiteral();
212
            }
213
214
            $this->checkLabelLength($prev);
215
216
            if ($this->isFWS()) {
217
                $this->parseFWS();
218
            }
219
220
            $domain .= $this->lexer->token['value'];
221
            $this->lexer->moveNext();
222
        } while (null !== $this->lexer->token['type']);
223
224
        $this->domainPart = $domain;
225
        return new ValidEmail();
226
    }
227
228
    private function checkNotAllowedChars(array $token)
229
    {
230
        $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
231
        if (isset($notAllowed[$token['type']])) {
232
            throw new CharNotAllowed();
233
        }
234
    }
235
236
    /**
237
     * @return string|false
238
     */
239
    protected function parseDomainLiteral()
240
    {
241
        if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
242
            $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
243
        }
244
        if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
245
            $lexer = clone $this->lexer;
246
            $lexer->moveNext();
247
            if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
248
                $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
249
            }
250
        }
251
252
        return $this->doParseDomainLiteral();
253
    }
254
255
    /**
256
     * @return string|false
257
     */
258
    protected function doParseDomainLiteral()
259
    {
260
        $IPv6TAG = false;
261
        $addressLiteral = '';
262
        do {
263
            if ($this->lexer->token['type'] === EmailLexer::C_NUL) {
264
                throw new ExpectingDTEXT();
265
            }
266
267
            if ($this->lexer->token['type'] === EmailLexer::INVALID ||
268
                $this->lexer->token['type'] === EmailLexer::C_DEL   ||
269
                $this->lexer->token['type'] === EmailLexer::S_LF
270
            ) {
271
                $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
272
            }
273
274
            if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) {
275
                throw new ExpectingDTEXT();
276
            }
277
278
            if ($this->lexer->isNextTokenAny(
279
                array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF)
280
            )) {
281
                $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
282
                $this->parseFWS();
283
            }
284
285
            if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
286
                throw new CRNoLF();
287
            }
288
289
            if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
290
                $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
291
                $addressLiteral .= $this->lexer->token['value'];
292
                $this->lexer->moveNext();
293
                $this->validateQuotedPair();
294
            }
295
            if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) {
296
                $IPv6TAG = true;
297
            }
298
            if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) {
299
                break;
300
            }
301
302
            $addressLiteral .= $this->lexer->token['value'];
303
304
        } while ($this->lexer->moveNext());
305
306
        $addressLiteral = str_replace('[', '', $addressLiteral);
307
        $addressLiteral = $this->checkIPV4Tag($addressLiteral);
308
309
        if (false === $addressLiteral) {
310
            return $addressLiteral;
311
        }
312
313
        if (!$IPv6TAG) {
314
            $this->warnings[DomainLiteral::CODE] = new DomainLiteral();
315
            return $addressLiteral;
316
        }
317
318
        $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
319
320
        $this->checkIPV6Tag($addressLiteral);
321
322
        return $addressLiteral;
323
    }
324
325
    /**
326
     * @param string $addressLiteral
327
     *
328
     * @return string|false
329
     */
330
    protected function checkIPV4Tag($addressLiteral)
331
    {
332
        $matchesIP  = array();
333
334
        // Extract IPv4 part from the end of the address-literal (if there is one)
335
        if (preg_match(
336
            '/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/',
337
            $addressLiteral,
338
            $matchesIP
339
        ) > 0
340
        ) {
341
            $index = strrpos($addressLiteral, $matchesIP[0]);
342
            if ($index === 0) {
343
                $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
344
                return false;
345
            }
346
            // Convert IPv4 part to IPv6 format for further testing
347
            $addressLiteral = substr($addressLiteral, 0, (int) $index) . '0:0';
348
        }
349
350
        return $addressLiteral;
351
    }
352
353
    protected function checkDomainPartExceptions(array $prev)
354
    {
355
        $invalidDomainTokens = array(
356
            EmailLexer::S_DQUOTE => true,
357
            EmailLexer::S_SEMICOLON => true,
358
            EmailLexer::S_GREATERTHAN => true,
359
            EmailLexer::S_LOWERTHAN => true,
360
        );
361
362
        if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
363
            throw new ExpectingATEXT();
364
        }
365
366
        if ($this->lexer->token['type'] === EmailLexer::S_COMMA) {
367
            throw new CommaInDomain();
368
        }
369
370
        if ($this->lexer->token['type'] === EmailLexer::S_AT) {
371
            throw new ConsecutiveAt();
372
        }
373
374
        if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) {
375
            throw new ExpectingATEXT();
376
        }
377
378 View Code Duplication
        if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
379
            throw new DomainHyphened();
380
        }
381
382 View Code Duplication
        if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
383
            && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
384
            throw new ExpectingATEXT();
385
        }
386
    }
387
388
    /**
389
     * @return bool
390
     */
391
    protected function hasBrackets()
392
    {
393
        if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) {
394
            return false;
395
        }
396
397
        try {
398
            $this->lexer->find(EmailLexer::S_CLOSEBRACKET);
399
        } catch (\RuntimeException $e) {
400
            throw new ExpectingDomainLiteralClose();
401
        }
402
403
        return true;
404
    }
405
406
    protected function checkLabelLength(array $prev)
407
    {
408
        if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
409
            $prev['type'] === EmailLexer::GENERIC &&
410
            strlen($prev['value']) > 63
411
        ) {
412
            $this->warnings[LabelTooLong::CODE] = new LabelTooLong();
413
        }
414
    }
415
416
    protected function addTLDWarnings()
417
    {
418
        if ($this->warnings[DomainLiteral::CODE]) {
419
            $this->warnings[TLD::CODE] = new TLD();
420
        }
421
    }
422
}
423