Completed
Push — master ( ed861e...6484b8 )
by Eduardo Gulias
02:04
created

DomainPart::hasBrackets()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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