Test Setup Failed
Push — parse-domain-literal ( caa29f...6abede )
by Eduardo Gulias
01:53
created

DomainLiteral::convertIPv4ToIPv6()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.6
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
namespace Egulias\EmailValidator\Parser;
3
4
use Egulias\EmailValidator\EmailLexer;
5
use Egulias\EmailValidator\Result\Reason\CRNoLF;
6
use Egulias\EmailValidator\Result\Reason\ExpectingDTEXT;
7
use Egulias\EmailValidator\Result\ValidEmail;
8
use Egulias\EmailValidator\Result\InvalidEmail;
9
use Egulias\EmailValidator\Warning\DomainLiteral as WarningDomainLiteral;
10
use Egulias\EmailValidator\Warning\CFWSWithFWS;
11
use Egulias\EmailValidator\Warning\IPV6BadChar;
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
21
class DomainLiteral extends Parser
22
{
23
    public function parse($remove)
24
    {
25
        $this->addTagWarnings();
26
27
        $IPv6TAG = false;
28
        $addressLiteral = '';
29
30
        do {
31 View Code Duplication
            if ($this->lexer->token['type'] === EmailLexer::C_NUL) {
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...
32
                return new InvalidEmail(new ExpectingDTEXT(), $this->lexer->token['value']);
33
            }
34
35
            $this->addObsoleteWarnings();
36
37 View Code Duplication
            if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) {
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...
38
                return new InvalidEmail(new ExpectingDTEXT(), $this->lexer->token['value']);
39
            }
40
41
            if ($this->lexer->isNextTokenAny(
42
                array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF)
43
            )) {
44
                $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
45
                $this->parseFWS();
46
            }
47
48 View Code Duplication
            if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
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...
49
                return new InvalidEmail(new CRNoLF(), $this->lexer->token['value']);
50
            }
51
52
            if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
53
                $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
54
                $addressLiteral .= $this->lexer->token['value'];
55
                $this->lexer->moveNext();
56
                $this->validateQuotedPair();
57
            }
58
            if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) {
59
                $IPv6TAG = true;
60
            }
61
62
            if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) {
63
                break;
64
            }
65
66
            $addressLiteral .= $this->lexer->token['value'];
67
68
        } while ($this->lexer->moveNext());
69
70
71
        //Encapsulate
72
        $addressLiteral = str_replace('[', '', $addressLiteral);
73
        $isAddressLiteralIPv4 = $this->checkIPV4Tag($addressLiteral);
74
75
        if (!$isAddressLiteralIPv4) {
76
            return new ValidEmail();
77
        } else {
78
            $addressLiteral = $this->convertIPv4ToIPv6($addressLiteral);
79
        }
80
81
        if (!$IPv6TAG) {
82
            $this->warnings[WarningDomainLiteral::CODE] = new WarningDomainLiteral();
83
            return new ValidEmail();
84
        }
85
86
        $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
87
88
        $this->checkIPV6Tag($addressLiteral);
89
90
        return new ValidEmail();
91
    }
92
93
    /**
94
     * @param string $addressLiteral
95
     * @param int $maxGroups
96
     */
97
    public function checkIPV6Tag($addressLiteral, $maxGroups = 8)
98
    {
99
        $prev = $this->lexer->getPrevious();
100
        if ($prev['type'] === EmailLexer::S_COLON) {
101
            $this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd();
102
        }
103
104
        $IPv6       = substr($addressLiteral, 5);
105
        //Daniel Marschall's new IPv6 testing strategy
106
        $matchesIP  = explode(':', $IPv6);
107
        $groupCount = count($matchesIP);
108
        $colons     = strpos($IPv6, '::');
109
110
        if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) {
111
            $this->warnings[IPV6BadChar::CODE] = new IPV6BadChar();
112
        }
113
114
        if ($colons === false) {
115
            // We need exactly the right number of groups
116
            if ($groupCount !== $maxGroups) {
117
                $this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount();
118
            }
119
            return;
120
        }
121
122
        if ($colons !== strrpos($IPv6, '::')) {
123
            $this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon();
124
            return;
125
        }
126
127
        if ($colons === 0 || $colons === (strlen($IPv6) - 2)) {
128
            // RFC 4291 allows :: at the start or end of an address
129
            //with 7 other groups in addition
130
            ++$maxGroups;
131
        }
132
133
        if ($groupCount > $maxGroups) {
134
            $this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups();
135
        } elseif ($groupCount === $maxGroups) {
136
            $this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated();
137
        }
138
    }
139
    
140
    public function convertIPv4ToIPv6($addressLiteralIPv4) : string
141
    {
142
        $matchesIP  = array();
143
        $IPv4Match = preg_match(
144
            '/\\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]?)$/',
145
            $addressLiteralIPv4,
146
            $matchesIP);
147
148
        // Extract IPv4 part from the end of the address-literal (if there is one)
149
        if ($IPv4Match > 0) {
150
            $index = strrpos($addressLiteralIPv4, $matchesIP[0]);
151
            //There's a match but it is at the start
152
            if ($index > 0) {
153
                // Convert IPv4 part to IPv6 format for further testing
154
                return substr($addressLiteralIPv4, 0, (int) $index) . '0:0';
155
            }
156
        }
157
158
        return $addressLiteralIPv4;
159
    }
160
161
    /**
162
     * @param string $addressLiteral
163
     *
164
     * @return string
165
     */
166
    protected function checkIPV4Tag($addressLiteral) : bool
167
    {
168
        $matchesIP  = array();
169
        $IPv4Match = preg_match(
170
            '/\\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]?)$/',
171
            $addressLiteral,
172
            $matchesIP);
173
174
        // Extract IPv4 part from the end of the address-literal (if there is one)
175
176
        if ($IPv4Match > 0) {
177
            $index = strrpos($addressLiteral, $matchesIP[0]);
178
            //There's a match but it is at the start
179
            if ($index === 0) {
180
                $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
181
                return false;
182
            }
183
        }
184
185
        return true;
186
    }
187
188
    private function addObsoleteWarnings()
189
    {
190
        if ($this->lexer->token['type'] === EmailLexer::INVALID ||
191
            $this->lexer->token['type'] === EmailLexer::C_DEL   ||
192
            $this->lexer->token['type'] === EmailLexer::S_LF
193
        ) {
194
            $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
195
        }
196
    }
197
198
    private function addTagWarnings()
199
    {
200
        if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
201
            $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
202
        }
203
        if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
204
            $lexer = clone $this->lexer;
205
            $lexer->moveNext();
206
            if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
207
                $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
208
            }
209
        }
210
    }
211
212
}