DomainLiteral::parse()   B
last analyzed

Complexity

Conditions 11
Paths 30

Size

Total Lines 64
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 11.1967

Importance

Changes 0
Metric Value
cc 11
eloc 35
nc 30
nop 0
dl 0
loc 64
ccs 30
cts 34
cp 0.8824
crap 11.1967
rs 7.3166
c 0
b 0
f 0

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\Result\Result;
7
use Egulias\EmailValidator\Result\ValidEmail;
8
use Egulias\EmailValidator\Result\InvalidEmail;
9
use Egulias\EmailValidator\Warning\CFWSWithFWS;
10
use Egulias\EmailValidator\Warning\IPV6BadChar;
11
use Egulias\EmailValidator\Result\Reason\CRNoLF;
12
use Egulias\EmailValidator\Warning\IPV6ColonEnd;
13
use Egulias\EmailValidator\Warning\IPV6MaxGroups;
14
use Egulias\EmailValidator\Warning\ObsoleteDTEXT;
15
use Egulias\EmailValidator\Warning\AddressLiteral;
16
use Egulias\EmailValidator\Warning\IPV6ColonStart;
17
use Egulias\EmailValidator\Warning\IPV6Deprecated;
18
use Egulias\EmailValidator\Warning\IPV6GroupCount;
19
use Egulias\EmailValidator\Warning\IPV6DoubleColon;
20
use Egulias\EmailValidator\Result\Reason\ExpectingDTEXT;
21
use Egulias\EmailValidator\Result\Reason\UnusualElements;
22
use Egulias\EmailValidator\Warning\DomainLiteral as WarningDomainLiteral;
23
24
class DomainLiteral extends PartParser
25
{
26
    public const IPV4_REGEX = '/\\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]?)$/';
27
28
    public const OBSOLETE_WARNINGS = [
29
        EmailLexer::INVALID,
30
        EmailLexer::C_DEL,
31
        EmailLexer::S_LF,
32
        EmailLexer::S_BACKSLASH
33
    ];
34 18
35
    public function parse(): Result
36 18
    {
37
        $this->addTagWarnings();
38 18
39 18
        $IPv6TAG = false;
40
        $addressLiteral = '';
41
42 18
        do {
43
            if ($this->lexer->current->isA(EmailLexer::C_NUL)) {
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\EmailLexer::C_NUL of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $types of Doctrine\Common\Lexer\Token::isA(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

43
            if ($this->lexer->current->isA(/** @scrutinizer ignore-type */ EmailLexer::C_NUL)) {
Loading history...
44
                return new InvalidEmail(new ExpectingDTEXT(), $this->lexer->current->value);
45
            }
46 18
47
            $this->addObsoleteWarnings();
48 18
49 1
            if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENBRACKET, EmailLexer::S_OPENBRACKET))) {
50
                return new InvalidEmail(new ExpectingDTEXT(), $this->lexer->current->value);
51
            }
52 17
53 17
            if ($this->lexer->isNextTokenAny(
54 17
                array(EmailLexer::S_HTAB, EmailLexer::S_SP, EmailLexer::CRLF)
55
            )) {
56
                $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
57
                $this->parseFWS();
58
            }
59 17
60 1
            if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
61
                return new InvalidEmail(new CRNoLF(), $this->lexer->current->value);
62
            }
63 16
64 1
            if ($this->lexer->current->isA(EmailLexer::S_BACKSLASH)) {
65
                return new InvalidEmail(new UnusualElements($this->lexer->current->value), $this->lexer->current->value);
66 16
            }
67 9
            if ($this->lexer->current->isA(EmailLexer::S_IPV6TAG)) {
68
                $IPv6TAG = true;
69
            }
70 16
71
            if ($this->lexer->current->isA(EmailLexer::S_CLOSEBRACKET)) {
72
                break;
73
            }
74 16
75
            $addressLiteral .= $this->lexer->current->value;
76 16
        } while ($this->lexer->moveNext());
77
78
79
        //Encapsulate
80 15
        $addressLiteral = str_replace('[', '', $addressLiteral);
81 15
        $isAddressLiteralIPv4 = $this->checkIPV4Tag($addressLiteral);
82
83 15
        if (!$isAddressLiteralIPv4) {
84 3
            return new ValidEmail();
85
        } else {
86 12
            $addressLiteral = $this->convertIPv4ToIPv6($addressLiteral);
87
        }
88
89 12
        if (!$IPv6TAG) {
90 3
            $this->warnings[WarningDomainLiteral::CODE] = new WarningDomainLiteral();
91 3
            return new ValidEmail();
92
        }
93
94 9
        $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
95
96 9
        $this->checkIPV6Tag($addressLiteral);
97
98 9
        return new ValidEmail();
99
    }
100
101
    /**
102
     * @param string $addressLiteral
103
     * @param int $maxGroups
104
     */
105 9
    public function checkIPV6Tag($addressLiteral, $maxGroups = 8): void
106
    {
107 9
        $prev = $this->lexer->getPrevious();
108 9
        if ($prev->isA(EmailLexer::S_COLON)) {
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\EmailLexer::S_COLON of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $types of Doctrine\Common\Lexer\Token::isA(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

108
        if ($prev->isA(/** @scrutinizer ignore-type */ EmailLexer::S_COLON)) {
Loading history...
109 1
            $this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd();
110
        }
111
112 9
        $IPv6       = substr($addressLiteral, 5);
113
        //Daniel Marschall's new IPv6 testing strategy
114 9
        $matchesIP  = explode(':', $IPv6);
115 9
        $groupCount = count($matchesIP);
116 9
        $colons     = strpos($IPv6, '::');
117
118 9
        if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) {
119 1
            $this->warnings[IPV6BadChar::CODE] = new IPV6BadChar();
120
        }
121
122 9
        if ($colons === false) {
123
            // We need exactly the right number of groups
124 4
            if ($groupCount !== $maxGroups) {
125 1
                $this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount();
126
            }
127 4
            return;
128
        }
129
130 5
        if ($colons !== strrpos($IPv6, '::')) {
131 1
            $this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon();
132 1
            return;
133
        }
134
135 4
        if ($colons === 0 || $colons === (strlen($IPv6) - 2)) {
136
            // RFC 4291 allows :: at the start or end of an address
137
            //with 7 other groups in addition
138 2
            ++$maxGroups;
139
        }
140
141 4
        if ($groupCount > $maxGroups) {
142 1
            $this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups();
143 3
        } elseif ($groupCount === $maxGroups) {
144 1
            $this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated();
145
        }
146
    }
147
148 12
    public function convertIPv4ToIPv6(string $addressLiteralIPv4): string
149
    {
150 12
        $matchesIP  = [];
151 12
        $IPv4Match = preg_match(self::IPV4_REGEX, $addressLiteralIPv4, $matchesIP);
152
153
        // Extract IPv4 part from the end of the address-literal (if there is one)
154 12
        if ($IPv4Match > 0) {
155 1
            $index = (int) strrpos($addressLiteralIPv4, $matchesIP[0]);
156
            //There's a match but it is at the start
157 1
            if ($index > 0) {
158
                // Convert IPv4 part to IPv6 format for further testing
159 1
                return substr($addressLiteralIPv4, 0, $index) . '0:0';
160
            }
161
        }
162
163 11
        return $addressLiteralIPv4;
164
    }
165
166
    /**
167
     * @param string $addressLiteral
168
     *
169
     * @return bool
170
     */
171 15
    protected function checkIPV4Tag($addressLiteral): bool
172
    {
173 15
        $matchesIP  = [];
174 15
        $IPv4Match = preg_match(self::IPV4_REGEX, $addressLiteral, $matchesIP);
175
176
        // Extract IPv4 part from the end of the address-literal (if there is one)
177
178 15
        if ($IPv4Match > 0) {
179 4
            $index = strrpos($addressLiteral, $matchesIP[0]);
180
            //There's a match but it is at the start
181 4
            if ($index === 0) {
182 3
                $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
183 3
                return false;
184
            }
185
        }
186
187 12
        return true;
188
    }
189
190 18
    private function addObsoleteWarnings(): void
191
    {
192 18
        if (in_array($this->lexer->current->type, self::OBSOLETE_WARNINGS)) {
193 2
            $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
194
        }
195
    }
196
197 18
    private function addTagWarnings(): void
198
    {
199 18
        if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
200
            $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
201
        }
202 18
        if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
203 9
            $lexer = clone $this->lexer;
204 9
            $lexer->moveNext();
205 9
            if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
206 1
                $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
207
            }
208
        }
209
    }
210
}
211