Completed
Push — 1.2 ( cbfe3e...b8bb14 )
by Eduardo Gulias
02:54
created

DomainPart::checkNotAllowedChars()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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