Passed
Branch 3.0.0-dev (36ce88)
by Eduardo Gulias
02:09
created

DomainPart::isLabelTooLong()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Egulias\EmailValidator\Parser;
4
5
use Egulias\EmailValidator\EmailLexer;
6
use Egulias\EmailValidator\Warning\TLD;
7
use Egulias\EmailValidator\Result\Result;
8
use Egulias\EmailValidator\Result\ValidEmail;
9
use Egulias\EmailValidator\Result\InvalidEmail;
10
use Egulias\EmailValidator\Result\Reason\DotAtEnd;
11
use Egulias\EmailValidator\Result\Reason\DotAtStart;
12
use Egulias\EmailValidator\Warning\DeprecatedComment;
13
use Egulias\EmailValidator\Result\Reason\CRLFAtTheEnd;
14
use Egulias\EmailValidator\Result\Reason\LabelTooLong;
15
use Egulias\EmailValidator\Result\Reason\NoDomainPart;
16
use Egulias\EmailValidator\Result\Reason\ConsecutiveAt;
17
use Egulias\EmailValidator\Result\Reason\DomainTooLong;
18
use Egulias\EmailValidator\Result\Reason\CharNotAllowed;
19
use Egulias\EmailValidator\Result\Reason\DomainHyphened;
20
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
21
use Egulias\EmailValidator\Parser\CommentStrategy\DomainComment;
22
use Egulias\EmailValidator\Result\Reason\ExpectingDomainLiteralClose;
23
use Egulias\EmailValidator\Parser\DomainLiteral as DomainLiteralParser;
24
25
class DomainPart extends Parser
26
{
27
    const DOMAIN_MAX_LENGTH = 253;
28
    const LABEL_MAX_LENGTH = 63;
29
30
    /**
31
     * @var string
32
     */
33
    protected $domainPart = '';
34
35
    /**
36
     * @var string
37
     */
38
    protected $label = '';
39
40 143
    public function parse() : Result
41
    {
42 143
        $this->lexer->moveNext();
43
44 143
        $domainChecks = $this->performDomainStartChecks();
45 143
        if ($domainChecks->isInvalid()) {
46 11
            return $domainChecks;
47
        }
48
49 132
        if ($this->lexer->token['type'] === EmailLexer::S_AT) {
50 1
            return new InvalidEmail(new ConsecutiveAt(), $this->lexer->token['value']);
51
        }
52 131
        $domain = $this->doParseDomainPart();
53 131
        if ($domain->isInvalid()) {
54 68
            return $domain;
55
        }
56
57 63
        $length = strlen($this->domainPart);
58
59 63
        $end = $this->checkEndOfDomain();
60 63
        if ($end->isInvalid()) {
61 3
            return $end;
62
        }
63
64 60
        if ($length > self::DOMAIN_MAX_LENGTH) {
65
            //$this->warnings[DomainTooLong::CODE] = new DomainTooLong();
66
            return new InvalidEmail(new DomainTooLong(), $this->lexer->token['value']);
67
        }
68
69 60
        return new ValidEmail();
70
    }
71
72 63
    private function checkEndOfDomain() : Result
73
    {
74 63
        $prev = $this->lexer->getPrevious();
75 63
        if ($prev['type'] === EmailLexer::S_DOT) {
76 2
            return new InvalidEmail(new DotAtEnd(), $this->lexer->token['value']);
77
        }
78 61
        if ($prev['type'] === EmailLexer::S_HYPHEN) {
79 1
            return new InvalidEmail(new DomainHyphened('Hypen found at the end of the domain'), $prev['value']);
80
        }
81
82 60
        if ($this->lexer->token['type'] === EmailLexer::S_SP) {
83
            return new InvalidEmail(new CRLFAtTheEnd(), $prev['value']);
84
        }
85 60
        return new ValidEmail();
86
87
    }
88
89 143
    private function performDomainStartChecks() : Result
90
    {
91 143
        $invalidTokens = $this->checkInvalidTokensAfterAT();
92 143
        if ($invalidTokens->isInvalid()) {
93 2
            return $invalidTokens;
94
        }
95
        
96 141
        $missingDomain = $this->checkEmptyDomain();
97 141
        if ($missingDomain->isInvalid()) {
98 9
            return $missingDomain;
99
        }
100
101 132
        if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
102 3
            $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
103
        }
104 132
        return new ValidEmail();
105
    }
106
107 141
    private function checkEmptyDomain() : Result
108
    {
109 141
        $thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY ||
110 133
            ($this->lexer->token['type'] === EmailLexer::S_SP &&
111 141
            !$this->lexer->isNextToken(EmailLexer::GENERIC));
112
113 141
        if ($thereIsNoDomain) {
114 9
            return new InvalidEmail(new NoDomainPart(), $this->lexer->token['value']);
115
        }
116
117 132
        return new ValidEmail();
118
    }
119
120 143
    private function checkInvalidTokensAfterAT() : Result
121
    {
122 143
        if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
123 1
            return new InvalidEmail(new DotAtStart(), $this->lexer->token['value']);
124
        }
125 142
        if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
126 1
            return new InvalidEmail(new DomainHyphened('After AT'), $this->lexer->token['value']);
127
        }
128 141
        return new ValidEmail();
129
    }
130
131
    /**
132
     * @return string
133
     */
134
    public function getDomainPart()
135
    {
136
        return $this->domainPart;
137
    }
138
139 7 View Code Duplication
    protected function parseComments(): Result
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...
140
    {
141 7
        $commentParser = new Comment($this->lexer, new DomainComment());
142 7
        $result = $commentParser->parse();
143 7
        $this->warnings = array_merge($this->warnings, $commentParser->getWarnings());
144
145 7
        return $result;
146
    }
147
148 131
    protected function doParseDomainPart() : Result
149
    {
150 131
        $tldMissing = true;
151 131
        $hasComments = false;
152 131
        $domain = '';
153
        do {
154 131
            $prev = $this->lexer->getPrevious();
155
156 131
            $notAllowedChars = $this->checkNotAllowedChars($this->lexer->token);
157 131
            if ($notAllowedChars->isInvalid()) {
158 3
                return $notAllowedChars;
159
            }
160
161 131 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...
162 131
                $this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS ) {
163 7
                $hasComments = true;
164 7
                $commentsResult = $this->parseComments();
165
166
                //Invalid comment parsing
167 7
                if($commentsResult->isInvalid()) {
168 5
                    return $commentsResult;
169
                }
170
            }
171
172 128
            $dotsResult = $this->checkConsecutiveDots();
173 128
            if ($dotsResult->isInvalid()) {
174 2
                return $dotsResult;
175
            }
176
177 128
            if ($this->lexer->token['type'] === EmailLexer::S_OPENBRACKET) {
178 18
                $literalResult = $this->parseDomainLiteral();
179
180 18
                $this->addTLDWarnings($tldMissing);
181 18
                return $literalResult;
182
            }
183
184 110
                $labelCheck = $this->checkLabelLength();
185 110
                if ($labelCheck->isInvalid()) {
186 5
                    return $labelCheck;
187
                }
188
189 110
            $FwsResult = $this->parseFWS();
190 110
            if($FwsResult->isInvalid()) {
191 4
                return $FwsResult;
192
            }
193
194 110
            $domain .= $this->lexer->token['value'];
195
196 110 View Code Duplication
            if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
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...
197 54
                $tldMissing = false;
198
            }
199
200 110
            $exceptionsResult = $this->checkDomainPartExceptions($prev, $hasComments);
201 110
            if ($exceptionsResult->isInvalid()) {
202 44
                return $exceptionsResult;
203
            }
204 108
            $this->lexer->moveNext();
205
206 108
        } while (null !== $this->lexer->token['type']);
207
208 50
        $labelCheck = $this->checkLabelLength(true);
209 50
        if ($labelCheck->isInvalid()) {
210 2
            return $labelCheck;
211
        }
212 48
        $this->addTLDWarnings($tldMissing);
213
214 48
        $this->domainPart = $domain;
215 48
        return new ValidEmail();
216
    }
217
218 131
    private function checkNotAllowedChars(array $token) : Result
219
    {
220 131
        $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
221 131
        if (isset($notAllowed[$token['type']])) {
222 3
            return new InvalidEmail(new CharNotAllowed(), $token['value']);
223
        }
224 131
        return new ValidEmail();
225
    }
226
227
    /**
228
     * @return Result
229
     */
230 18
    protected function parseDomainLiteral() : Result
231
    {
232
233
        try {
234 18
            $this->lexer->find(EmailLexer::S_CLOSEBRACKET);
235
        } catch (\RuntimeException $e) {
236
            return new InvalidEmail(new ExpectingDomainLiteralClose(), $this->lexer->token['value']);
237
        }
238
239 18
        $domainLiteralParser = new DomainLiteralParser($this->lexer);
240 18
        $result = $domainLiteralParser->parse();
241 18
        $this->warnings = array_merge($this->warnings, $domainLiteralParser->getWarnings());
242 18
        return $result;
243
    }
244
245
    /**
246
     * @return InvalidEmail|ValidEmail
247
     */
248 110
    protected function checkDomainPartExceptions(array $prev, bool $hasComments) : Result
249
    {
250
        $validDomainTokens = array(
251 110
            EmailLexer::GENERIC => true,
252
            EmailLexer::S_HYPHEN => true,
253
            EmailLexer::S_DOT => true,
254
        );
255
256 110
        if ($hasComments) {
257 2
            $validDomainTokens[EmailLexer::S_OPENPARENTHESIS] = true;
258 2
            $validDomainTokens[EmailLexer::S_CLOSEPARENTHESIS] = true;
259
        }
260
261 110
        if ($this->lexer->token['type'] === EmailLexer::S_OPENBRACKET && $prev['type'] !== EmailLexer::S_AT) {
262
            return new InvalidEmail(new ExpectingATEXT('OPENBRACKET not after AT'), $this->lexer->token['value']);
263
        }
264
265 110 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...
266 1
            return new InvalidEmail(new DomainHyphened('Hypen found near DOT'), $this->lexer->token['value']);
267
        }
268
269 110 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...
270 110
            && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
271
            return new InvalidEmail(new ExpectingATEXT('Escaping following "ATOM"'), $this->lexer->token['value']);
272
        }
273
274 110
        if (!isset($validDomainTokens[$this->lexer->token['type']])) {
275 43
            return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token['value']), $this->lexer->token['value']);
276
        }
277
278 108
        return new ValidEmail();
279
    }
280
281
282 110
    private function checkLabelLength(bool $isEndOfDomain = false) : Result
283
    {
284 110 View Code Duplication
        if ($this->lexer->token['type'] === EmailLexer::S_DOT || $isEndOfDomain) {
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...
285 67
            if ($this->isLabelTooLong($this->label)) {
286 7
                $this->label = '';
287 7
                return new InvalidEmail(new LabelTooLong(), $this->lexer->token['value']);
288
            }
289
        }
290 110
        $this->label .= $this->lexer->token['value'];
291 110
        return new ValidEmail();
292
    }
293
294
295 67
    private function isLabelTooLong(string $label) : bool
296
    {
297 67
        if (preg_match('/[^\x00-\x7F]/', $label)) {
298 9
            idn_to_ascii(utf8_decode($label), IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
299 9
            return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG);
300
        }
301 59
        return strlen($label) > self::LABEL_MAX_LENGTH;
302
    }
303
304 66
    private function addTLDWarnings(bool $isTLDMissing) : void
305
    {
306 66
        if ($isTLDMissing) {
307 23
            $this->warnings[TLD::CODE] = new TLD();
308
        }
309
    }
310
}