Passed
Pull Request — master (#194)
by
unknown
02:41
created

MailboxCheckValidation::getFromEmail()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
3
namespace Egulias\EmailValidator\Validation;
4
5
use Egulias\EmailValidator\EmailLexer;
6
use Egulias\EmailValidator\Exception\InvalidEmail;
7
use Egulias\EmailValidator\Validation\Error\IllegalMailbox;
8
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
9
use Egulias\EmailValidator\Warning\SocketWarning;
10
use Egulias\EmailValidator\Warning\Warning;
11
12
class MailboxCheckValidation implements EmailValidation
13
{
14
    const END_OF_LINE = "\r\n";
15
16
    /**
17
     * @var InvalidEmail
18
     */
19
    private $error;
20
21
    /**
22
     * @var Warning[]
23
     */
24
    private $warnings = [];
25
26
    /**
27
     * @var int
28
     */
29
    private $lastResponseCode;
30
31
    /**
32
     * @var int
33
     */
34
    private $port = 25;
35
36
    /**
37
     * @var int
38
     */
39
    private $timeout = 10;
40
41
    /**
42
     * @var string
43
     */
44
    private $fromEmail = '[email protected]';
45
46
    /**
47
     * MailboxCheckValidation constructor.
48
     */
49 4
    public function __construct()
50
    {
51 4
        if (!extension_loaded('intl')) {
52
            throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
53
        }
54 4
    }
55
56
    /**
57
     * @inheritDoc
58
     */
59 1
    public function getError()
60
    {
61 1
        return $this->error;
62
    }
63
64
    /**
65
     * @inheritDoc
66
     */
67 1
    public function getWarnings()
68
    {
69 1
        return $this->warnings;
70
    }
71
72
    /**
73
     * @return int
74
     */
75
    public function getLastResponseCode()
76
    {
77
        return $this->lastResponseCode;
78
    }
79
80
    /**
81
     * @return int
82
     */
83
    public function getPort()
84
    {
85
        return $this->port;
86
    }
87
88
    /**
89
     * @param int $port
90
     */
91
    public function setPort($port)
92
    {
93
        $this->port = $port;
94
    }
95
96
    /**
97
     * @return int
98
     */
99
    public function getTimeout()
100
    {
101
        return $this->timeout;
102
    }
103
104
    /**
105
     * @param int $timeout
106
     */
107
    public function setTimeout($timeout)
108
    {
109
        $this->timeout = $timeout;
110
    }
111
112
    /**
113
     * @return string
114
     */
115
    public function getFromEmail()
116
    {
117
        return $this->fromEmail;
118
    }
119
120
    /**
121
     * @param string $fromEmail
122
     */
123
    public function setFromEmail($fromEmail)
124
    {
125
        $this->fromEmail = $fromEmail;
126
    }
127
128
    /**
129
     * @inheritDoc
130
     */
131 4
    public function isValid($email, EmailLexer $emailLexer)
132
    {
133 4
        $mxHosts = $this->getMXHosts($email);
134
135 4
        $isValid = false;
136 4
        foreach ($mxHosts as $mxHost) {
137 2
            if ( ($isValid = $this->checkMailbox($mxHost, $this->port, $this->timeout, $this->fromEmail, $email)) ) {
138 1
                break;
139
            }
140 4
        }
141
142 4
        if ( ! $isValid ) {
143 3
            $this->error = new IllegalMailbox($this->lastResponseCode);
144 3
        }
145
146 4
        return $this->error === null;
147
    }
148
149
    /**
150
     * @param string $email
151
     *
152
     * @return array
153
     */
154 4
    protected function getMXHosts($email)
155
    {
156 4
        $variant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : INTL_IDNA_VARIANT_2003;
157
158 4
        $hostname = $email;
159 4 View Code Duplication
        if ( false !== ($lastAtPos = strrpos($email, '@')) ) {
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...
160 4
            $hostname = substr($email, $lastAtPos + 1);
161 4
        }
162 4
        $hostname = rtrim(idn_to_ascii($hostname, IDNA_DEFAULT, $variant), '.') . '.';
163
164 4
        $mxHosts = [];
165 4
        $result = getmxrr($hostname, $mxHosts);
166 4
        if ( ! $result ) {
167 2
            $this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord();
168 2
        }
169
170 4
        return $mxHosts;
171
    }
172
173
    /**
174
     * @param string $hostname
175
     * @param int $port
176
     * @param int $timeout
177
     * @param string $fromEmail
178
     * @param string $toEmail
179
     * @return bool
180
     */
181 2
    protected function checkMailbox($hostname, $port, $timeout, $fromEmail, $toEmail)
182
    {
183 2
        $socket = @fsockopen($hostname, $port, $errno, $errstr, $timeout);
184
185 2
        if ( ! $socket ) {
186
            $this->warnings[SocketWarning::CODE][] = new SocketWarning($hostname, $errno, $errstr);
187
188
            return false;
189
        }
190
191 2
        if ( ! ($this->assertResponse($socket, 220) ) ) {
192
            return false;
193
        }
194
195 2
        fwrite($socket, "EHLO {$hostname}" . self::END_OF_LINE);
196 2
        if ( ! ($this->assertResponse($socket, 250) ) ) {
197
            return false;
198
        }
199
200 2
        fwrite($socket, "MAIL FROM: <{$fromEmail}>" . self::END_OF_LINE);
201 2
        if ( ! ($this->assertResponse($socket, 250) ) ) {
202
            return false;
203
        }
204
205 2
        fwrite($socket, "RCPT TO: <{$toEmail}>" . self::END_OF_LINE);
206 2
        if ( ! ($this->assertResponse($socket, 250) ) ) {
207 1
            return false;
208
        }
209
210 1
        fwrite($socket, 'QUIT' . self::END_OF_LINE);
211
212 1
        fclose($socket);
213
214 1
        return true;
215
    }
216
217
    /**
218
     * @param resource $socket
219
     * @param int $expectedCode
220
     *
221
     * @return bool
222
     */
223 2
    private function assertResponse($socket, $expectedCode)
224
    {
225 2
        if ( ! is_resource($socket) ) {
226
            return false;
227
        }
228
229 2
        $data = '';
230 2
        while (substr($data, 3, 1) !== ' ') {
231 2
            if ( ! ( $data = @fgets($socket, 256) ) ) {
232
                $this->lastResponseCode = -1;
233
234
                return false;
235
            }
236 2
        }
237
238 2
        return ($this->lastResponseCode = intval( substr($data, 0, 3) )) === $expectedCode;
239
    }
240
}