Passed
Pull Request — master (#278)
by
unknown
01:57
created

DomainPart::doParseDomainPart()   B

Complexity

Conditions 10
Paths 99

Size

Total Lines 54

Duplication

Lines 7
Ratio 12.96 %

Code Coverage

Tests 37
CRAP Score 10.0135

Importance

Changes 0
Metric Value
dl 7
loc 54
ccs 37
cts 39
cp 0.9487
rs 7.1369
c 0
b 0
f 0
cc 10
nc 99
nop 0
crap 10.0135

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\DotAtStart;
14
use Egulias\EmailValidator\Exception\ExpectingATEXT;
15
use Egulias\EmailValidator\Exception\ExpectingDomainLiteralClose;
16
use Egulias\EmailValidator\Exception\ExpectingDTEXT;
17
use Egulias\EmailValidator\Exception\NoDomainPart;
18
use Egulias\EmailValidator\Exception\UnopenedComment;
19
use Egulias\EmailValidator\Warning\AddressLiteral;
20
use Egulias\EmailValidator\Warning\CFWSWithFWS;
21
use Egulias\EmailValidator\Warning\DeprecatedComment;
22
use Egulias\EmailValidator\Warning\DomainLiteral;
23
use Egulias\EmailValidator\Warning\DomainTooLong;
24
use Egulias\EmailValidator\Warning\IPV6BadChar;
25
use Egulias\EmailValidator\Warning\IPV6ColonEnd;
26
use Egulias\EmailValidator\Warning\IPV6ColonStart;
27
use Egulias\EmailValidator\Warning\IPV6Deprecated;
28
use Egulias\EmailValidator\Warning\IPV6DoubleColon;
29
use Egulias\EmailValidator\Warning\IPV6GroupCount;
30
use Egulias\EmailValidator\Warning\IPV6MaxGroups;
31
use Egulias\EmailValidator\Warning\LabelTooLong;
32
use Egulias\EmailValidator\Warning\ObsoleteDTEXT;
33
use Egulias\EmailValidator\Warning\TLD;
34
35
class DomainPart extends Parser
36
{
37
    const DOMAIN_MAX_LENGTH = 254;
38
    const LABEL_MAX_LENGTH = 63;
39
40
    /**
41
     * @var string
42
     */
43
    protected $domainPart = '';
44
45 100
    public function parse($domainPart)
46
    {
47 100
        $this->lexer->moveNext();
48
49 100
        $this->performDomainStartChecks();
50
51 90
        $domain = $this->doParseDomainPart();
52
53 59
        $prev = $this->lexer->getPrevious();
54 59
        $length = strlen($domain);
55
56 59
        if ($prev['type'] === EmailLexer::S_DOT) {
57 2
            throw new DotAtEnd();
58
        }
59 57
        if ($prev['type'] === EmailLexer::S_HYPHEN) {
60 1
            throw new DomainHyphened();
61
        }
62 56
        if ($length > self::DOMAIN_MAX_LENGTH) {
63 2
            $this->warnings[DomainTooLong::CODE] = new DomainTooLong();
64 2
        }
65 56
        if ($prev['type'] === EmailLexer::S_CR) {
66
            throw new CRLFAtTheEnd();
67
        }
68 56
        $this->domainPart = $domain;
69 56
    }
70
71 100
    private function performDomainStartChecks()
72
    {
73 100
        $this->checkInvalidTokensAfterAT();
74 99
        $this->checkEmptyDomain();
75
76 92
        if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
77 3
            $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
78 3
            $this->parseDomainComments();
79 1
        }
80 90
    }
81
82 99
    private function checkEmptyDomain()
83
    {
84 99
        $thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY ||
85 93
            ($this->lexer->token['type'] === EmailLexer::S_SP &&
86 99
            !$this->lexer->isNextToken(EmailLexer::GENERIC));
87
88 99
        if ($thereIsNoDomain) {
89 7
            throw new NoDomainPart();
90
        }
91 92
    }
92
93 100
    private function checkInvalidTokensAfterAT()
94
    {
95 100
        if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
96 1
            throw new DotAtStart();
97
        }
98 99
        if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
99
            throw new DomainHyphened();
100
        }
101 99
    }
102
103
    /**
104
     * @return string
105
     */
106 56
    public function getDomainPart()
107
    {
108 56
        return $this->domainPart;
109
    }
110
111
    /**
112
     * @param string $addressLiteral
113
     * @param int $maxGroups
114
     */
115 7
    public function checkIPV6Tag($addressLiteral, $maxGroups = 8)
116
    {
117 7
        $prev = $this->lexer->getPrevious();
118 7
        if ($prev['type'] === EmailLexer::S_COLON) {
119 1
            $this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd();
120 1
        }
121
122 7
        $IPv6       = substr($addressLiteral, 5);
123
        //Daniel Marschall's new IPv6 testing strategy
124 7
        $matchesIP  = explode(':', $IPv6);
125 7
        $groupCount = count($matchesIP);
126 7
        $colons     = strpos($IPv6, '::');
127
128 7
        if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) {
129 1
            $this->warnings[IPV6BadChar::CODE] = new IPV6BadChar();
130 1
        }
131
132 7
        if ($colons === false) {
133
            // We need exactly the right number of groups
134 4
            if ($groupCount !== $maxGroups) {
135 1
                $this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount();
136 1
            }
137 4
            return;
138
        }
139
140 3
        if ($colons !== strrpos($IPv6, '::')) {
141 1
            $this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon();
142 1
            return;
143
        }
144
145 2
        if ($colons === 0 || $colons === (strlen($IPv6) - 2)) {
146
            // RFC 4291 allows :: at the start or end of an address
147
            //with 7 other groups in addition
148 2
            ++$maxGroups;
149 2
        }
150
151 2
        if ($groupCount > $maxGroups) {
152 1
            $this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups();
153 2
        } elseif ($groupCount === $maxGroups) {
154 1
            $this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated();
155 1
        }
156 2
    }
157
158
    /**
159
     * @return string
160
     */
161 90
    protected function doParseDomainPart()
162
    {
163 90
        $domain = '';
164 90
        $label = '';
165 90
        $openedParenthesis = 0;
166
        do {
167 90
            $prev = $this->lexer->getPrevious();
168
169 90
            $this->checkNotAllowedChars($this->lexer->token);
170
171 90
            if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
172 3
                $this->parseComments();
173 3
                $openedParenthesis += $this->getOpenedParenthesis();
174 3
                $this->lexer->moveNext();
175 3
                $tmpPrev = $this->lexer->getPrevious();
176 3
                if ($tmpPrev['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
177 3
                    $openedParenthesis--;
178 3
                }
179 3
            }
180 90 View Code Duplication
            if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
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...
181 3
                if ($openedParenthesis === 0) {
182 3
                    throw new UnopenedComment();
183
                } else {
184
                    $openedParenthesis--;
185
                }
186
            }
187
188 89
            $this->checkConsecutiveDots();
189 89
            $this->checkDomainPartExceptions($prev);
190
191 88
            if ($this->hasBrackets()) {
192 13
                $this->parseDomainLiteral();
193 11
            }
194
195 86
            if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
196 48
                $this->checkLabelLength($label);
197 48
                $label = '';
198 48
            } else {
199 86
                $label .= $this->lexer->token['value'];
200
            }
201
202 86
            if ($this->isFWS()) {
203 3
                $this->parseFWS();
204 2
            }
205
206 86
            $domain .= $this->lexer->token['value'];
207 86
            $this->lexer->moveNext();
208 86
            if ($this->lexer->token['type'] === EmailLexer::S_SP) {
209 8
                throw new CharNotAllowed();
210
            }
211 86
        } while (null !== $this->lexer->token['type']);
212
213 59
        return $domain;
214
    }
215
216 90
    private function checkNotAllowedChars(array $token)
217
    {
218 90
        $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
219 90
        if (isset($notAllowed[$token['type']])) {
220 3
            throw new CharNotAllowed();
221
        }
222 90
    }
223
224
    /**
225
     * @return string|false
226
     */
227 13
    protected function parseDomainLiteral()
228
    {
229 13
        if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
230
            $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
231
        }
232 13
        if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
233 7
            $lexer = clone $this->lexer;
234 7
            $lexer->moveNext();
235 7
            if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
236 1
                $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
237 1
            }
238 7
        }
239
240 13
        return $this->doParseDomainLiteral();
241
    }
242
243
    /**
244
     * @return string|false
245
     */
246 13
    protected function doParseDomainLiteral()
247
    {
248 13
        $IPv6TAG = false;
249 13
        $addressLiteral = '';
250
        do {
251 13
            if ($this->lexer->token['type'] === EmailLexer::C_NUL) {
252
                throw new ExpectingDTEXT();
253
            }
254
255 13
            if ($this->lexer->token['type'] === EmailLexer::INVALID ||
256 13
                $this->lexer->token['type'] === EmailLexer::C_DEL   ||
257 13
                $this->lexer->token['type'] === EmailLexer::S_LF
258 13
            ) {
259 1
                $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
260 1
            }
261
262 13
            if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) {
263 1
                throw new ExpectingDTEXT();
264
            }
265
266 12
            if ($this->lexer->isNextTokenAny(
267 12
                array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF)
268 12
            )) {
269
                $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
270
                $this->parseFWS();
271
            }
272
273 12
            if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
274 1
                throw new CRNoLF();
275
            }
276
277 11
            if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
278
                $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
279
                $addressLiteral .= $this->lexer->token['value'];
280
                $this->lexer->moveNext();
281
                $this->validateQuotedPair();
282
            }
283 11
            if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) {
284 7
                $IPv6TAG = true;
285 7
            }
286 11
            if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) {
287
                break;
288
            }
289
290 11
            $addressLiteral .= $this->lexer->token['value'];
291
292 11
        } while ($this->lexer->moveNext());
293
294 11
        $addressLiteral = str_replace('[', '', $addressLiteral);
295 11
        $addressLiteral = $this->checkIPV4Tag($addressLiteral);
296
297 11
        if (false === $addressLiteral) {
298 1
            return $addressLiteral;
299
        }
300
301 10
        if (!$IPv6TAG) {
302 3
            $this->warnings[DomainLiteral::CODE] = new DomainLiteral();
303 3
            return $addressLiteral;
304
        }
305
306 7
        $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
307
308 7
        $this->checkIPV6Tag($addressLiteral);
309
310 7
        return $addressLiteral;
311
    }
312
313
    /**
314
     * @param string $addressLiteral
315
     *
316
     * @return string|false
317
     */
318 11
    protected function checkIPV4Tag($addressLiteral)
319
    {
320 11
        $matchesIP  = array();
321
322
        // Extract IPv4 part from the end of the address-literal (if there is one)
323 11
        if (preg_match(
324 11
            '/\\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]?)$/',
325 11
            $addressLiteral,
326
            $matchesIP
327 11
        ) > 0
328 11
        ) {
329 2
            $index = strrpos($addressLiteral, $matchesIP[0]);
330 2
            if ($index === 0) {
331 1
                $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
332 1
                return false;
333
            }
334
            // Convert IPv4 part to IPv6 format for further testing
335 1
            $addressLiteral = substr($addressLiteral, 0, (int) $index) . '0:0';
336 1
        }
337
338 10
        return $addressLiteral;
339
    }
340
341 89
    protected function checkDomainPartExceptions(array $prev)
342
    {
343
        $invalidDomainTokens = array(
344 89
            EmailLexer::S_DQUOTE => true,
345 89
            EmailLexer::S_SQUOTE => true,
346 89
            EmailLexer::S_BACKTICK => true,
347 89
            EmailLexer::S_SEMICOLON => true,
348 89
            EmailLexer::S_GREATERTHAN => true,
349 89
            EmailLexer::S_LOWERTHAN => true,
350 89
        );
351
352 89
        if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
353 7
            throw new ExpectingATEXT();
354
        }
355
356 89
        if ($this->lexer->token['type'] === EmailLexer::S_COMMA) {
357 1
            throw new CommaInDomain();
358
        }
359
360 89
        if ($this->lexer->token['type'] === EmailLexer::S_AT) {
361 2
            throw new ConsecutiveAt();
362
        }
363
364 88 View Code Duplication
        if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) {
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...
365 1
            throw new ExpectingATEXT();
366
        }
367
368 88 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...
369 1
            throw new DomainHyphened();
370
        }
371
372 88 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...
373 88
            && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
374
            throw new ExpectingATEXT();
375
        }
376 88
    }
377
378
    /**
379
     * @return bool
380
     */
381 88
    protected function hasBrackets()
382
    {
383 88
        if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) {
384 75
            return false;
385
        }
386
387
        try {
388 13
            $this->lexer->find(EmailLexer::S_CLOSEBRACKET);
389 13
        } catch (\RuntimeException $e) {
390
            throw new ExpectingDomainLiteralClose();
391
        }
392
393 13
        return true;
394
    }
395
396
    /**
397
     * @param string $label
398
     */
399 48
    protected function checkLabelLength($label)
400
    {
401 48
        if ($this->isLabelTooLong($label)) {
402 3
            $this->warnings[LabelTooLong::CODE] = new LabelTooLong();
403 3
        }
404 48
    }
405
406
    /**
407
     * @param string $label
408
     * @return bool
409
     */
410 48
    private function isLabelTooLong($label)
411
    {
412 48
        if (preg_match('/[^\x00-\x7F]/', $label)) {
413 5
            $label = idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
0 ignored issues
show
Bug introduced by
The variable $idnaInfo does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
414
415 5
            if (!$label) {
416 1
                return (bool) ($idnaInfo['errors'] | IDNA_ERROR_LABEL_TOO_LONG);
417
            }
418 4
        }
419
420 47
        return strlen($label) > self::LABEL_MAX_LENGTH;
421
    }
422
423 3
    protected function parseDomainComments()
424
    {
425 3
        $this->isUnclosedComment();
426 1
        while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
427 1
            $this->warnEscaping();
428 1
            $this->lexer->moveNext();
429 1
        }
430
431 1
        $this->lexer->moveNext();
432 1
        if ($this->lexer->isNextToken(EmailLexer::S_DOT)) {
433
            throw new ExpectingATEXT();
434
        }
435 1
    }
436
437
    protected function addTLDWarnings()
438
    {
439
        if ($this->warnings[DomainLiteral::CODE]) {
440
            $this->warnings[TLD::CODE] = new TLD();
441
        }
442
    }
443
}
444