Test Failed
Push — master ( f47101...348e9e )
by Ricardo
02:03
created

Email::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Validate;
4
5
use Exception;
6
7
use Validate\Traits\FakeNameTrait;
8
9
class Email implements \Validate\Contracts\Validate
10
{
11
    use FakeNameTrait;
12
13
    protected $stream = false; 
14
15
    /** 
16
     * SMTP port number 
17
     * @var int 
18
     */ 
19
    protected $port = 25; 
20
21
    /** 
22
     * Email address for request 
23
     * @var string 
24
     */ 
25
    protected $from = 'root@localhost'; 
26
27
    /** 
28
     * The connection timeout, in seconds. 
29
     * @var int 
30
     */ 
31
    protected $max_connection_timeout = 30; 
32
33
    /** 
34
     * Timeout value on stream, in seconds. 
35
     * @var int 
36
     */ 
37
    protected $stream_timeout = 5; 
38
39
    /** 
40
     * Wait timeout on stream, in seconds. 
41
     * * 0 - not wait 
42
     * @var int 
43
     */ 
44
    protected $stream_timeout_wait = 0; 
45
46
    /** 
47
     * Whether to throw exceptions for errors. 
48
     * @type boolean 
49
     * @access protected 
50
     */ 
51
    protected $exceptions = false; 
52
53
    /** 
54
     * The number of errors encountered. 
55
     * @type integer 
56
     * @access protected 
57
     */ 
58
    protected $error_count = 0; 
59
60
    /** 
61
     * class debug output mode. 
62
     * @type boolean 
63
     */ 
64
    public $Debug = false; 
65
66
    /** 
67
     * How to handle debug output. 
68
     * Options: 
69
     * * `echo` Output plain-text as-is, appropriate for CLI 
70
     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output 
71
     * * `log` Output to error log as configured in php.ini 
72
     * @type string 
73
     */ 
74
    public $Debugoutput = 'echo'; 
75
76
    /** 
77
     * SMTP RFC standard line ending. 
78
     */ 
79
    const CRLF = "\r\n"; 
80
81
    /** 
82
     * Holds the most recent error message. 
83
     * @type string 
84
     */ 
85
    public $ErrorInfo = ''; 
86
87
    /** 
88
     * Constructor. 
89
     * @param boolean $exceptions Should we throw external exceptions? 
90
     */ 
91
    public function __construct($exceptions = false) { 
92
        $this->exceptions = (boolean) $exceptions; 
93
    } 
94
95
    public static function isSame(string $to, string $from)
96
    {
97
        return (self::toDatabase($to)===self::toDatabase($from));
98
    }
99
100
    /** 
101
     * Validate email address. 
102
     * @param string $email 
103
     * @return boolean True if valid. 
104
     */ 
105
    public static function validate($email)
106
    {
107
        if ((boolean) filter_var($email, FILTER_VALIDATE_EMAIL)===false){
108
            return false;
109
        }
110
        
111
        $emailAddresse = explode("@", trim($email));
112
113
        if (static::incluiInArray($emailAddresse[0], static::$notPermit)) {
114
            return false;
115
        }
116
117
        if (static::incluiInArray($emailAddresse[0], static::$notPermitInFirst)) {
118
            return false;
119
        }
120
121
        if (!checkdnsrr($emailAddresse[1], "MX")) { 
122
            return false;
123
        } 
124
125
        return true;
126
    }
127
128
    /**
129
     * Break Email
130
     */
131
    public static function toDatabase($email)
132
    {
133
        return $email;
134
    }
135
136
    /**
137
     * Break Email
138
     */
139
    public static function toUser($email)
140
    {
141
        return $email;
142
    }
143
144
    /**
145
     * Break Email
146
     */
147
    public static function break($email)
148
    {
149
        $email = explode('@', $email);
150
        $data['address'] = $email[0];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = array(); before regardless.
Loading history...
151
        $data['domain'] = $email[1];
152
        return $data;
153
    }
154
155
    /** 
156
     * Set email address for SMTP request 
157
     * @param string $email Email address 
158
     */ 
159
    public function setEmailFrom($email) { 
160
        if (!self::validate($email)) { 
161
            $this->set_error('Invalid address : ' . $email); 
162
            $this->edebug($this->ErrorInfo); 
163
            if ($this->exceptions) { 
164
                throw new Exception($this->ErrorInfo); 
165
            } 
166
        } 
167
        $this->from = $email; 
168
    } 
169
170
    /** 
171
     * Set connection timeout, in seconds. 
172
     * @param int $seconds 
173
     */ 
174
    public function setConnectionTimeout($seconds) { 
175
        if ($seconds > 0) { 
176
            $this->max_connection_timeout = (int) $seconds; 
177
        } 
178
    } 
179
180
    /** 
181
     * Sets the timeout value on stream, expressed in the seconds 
182
     * @param int $seconds 
183
     */ 
184
    public function setStreamTimeout($seconds) { 
185
        if ($seconds > 0) { 
186
            $this->stream_timeout = (int) $seconds; 
187
        } 
188
    } 
189
190
    public function setStreamTimeoutWait($seconds) { 
191
        if ($seconds >= 0) { 
192
            $this->stream_timeout_wait = (int) $seconds; 
193
        } 
194
    } 
195
196
    /** 
197
     * Get array of MX records for host. Sort by weight information. 
198
     * @param string $hostname The Internet host name. 
199
     * @return array Array of the MX records found. 
200
     */ 
201
    public function getMXrecords($hostname) { 
202
        $mxhosts = array(); 
203
        $mxweights = array(); 
204
        if (getmxrr($hostname, $mxhosts, $mxweights) === FALSE) { 
205
            $this->set_error('MX records not found or an error occurred'); 
206
            $this->edebug($this->ErrorInfo); 
207
        } else { 
208
            array_multisort($mxweights, $mxhosts); 
209
        } 
210
        /** 
211
         * Add A-record as last chance (e.g. if no MX record is there). 
212
         * Thanks Nicht Lieb. 
213
         * @link http://www.faqs.org/rfcs/rfc2821.html RFC 2821 - Simple Mail Transfer Protocol 
214
         */ 
215
        if (empty($mxhosts)) { 
216
            $mxhosts[] = $hostname; 
217
        } 
218
        return $mxhosts; 
219
    } 
220
221
    /** 
222
     * Parses input string to array(0=>user, 1=>domain) 
223
     * @param string $email 
224
     * @param boolean $only_domain 
225
     * @return string|array 
226
     * @access private 
227
     */ 
228
    public static function parse_email($email, $only_domain = TRUE) { 
229
        sscanf($email, "%[^@]@%s", $user, $domain); 
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $domain does not exist. Did you maybe mean $only_domain?
Loading history...
230
        return ($only_domain) ? $domain : array($user, $domain); 
231
    } 
232
233
    /** 
234
     * Add an error message to the error container. 
235
     * @access protected 
236
     * @param string $msg 
237
     * @return void 
238
     */ 
239
    protected function set_error($msg) { 
240
        $this->error_count++; 
241
        $this->ErrorInfo = $msg; 
242
    } 
243
244
    /** 
245
     * Check if an error occurred. 
246
     * @access public 
247
     * @return boolean True if an error did occur. 
248
     */ 
249
    public function isError() { 
250
        return ($this->error_count > 0); 
251
    } 
252
253
    /** 
254
     * Output debugging info 
255
     * Only generates output if debug output is enabled 
256
     * @see verifyEmail::$Debugoutput 
257
     * @see verifyEmail::$Debug 
258
     * @param string $str 
259
     */ 
260
    protected function edebug($str) { 
261
        if (!$this->Debug) { 
262
            return; 
263
        } 
264
        switch ($this->Debugoutput) { 
265
            case 'log': 
266
                //Don't output, just log 
267
                error_log($str); 
268
                break; 
269
            case 'html': 
270
                //Cleans up output a bit for a better looking, HTML-safe output 
271
                echo htmlentities( 
272
                        preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, 'UTF-8' 
273
                ) 
274
                . "<br>\n"; 
275
                break; 
276
            case 'echo': 
277
            default: 
278
                //Normalize line breaks 
279
                $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); 
280
                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( 
281
                        "\n", "\n \t ", trim($str) 
282
                ) . "\n"; 
283
        } 
284
    } 
285
286
    /** 
287
     * Validate email
288
     * @param string $email Email address 
289
     * @return boolean True if the valid email also exist 
290
     */ 
291
    public function check($email) { 
292
        $result = FALSE; 
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
293
294
        if (!self::validate($email)) { 
295
            $this->set_error("{$email} incorrect e-mail"); 
296
            $this->edebug($this->ErrorInfo); 
297
            if ($this->exceptions) { 
298
                throw new Exception($this->ErrorInfo); 
299
            } 
300
            return FALSE; 
301
        } 
302
        $this->error_count = 0; // Reset errors 
303
        $this->stream = FALSE; 
304
305
        $mxs = $this->getMXrecords(self::parse_email($email)); 
0 ignored issues
show
Bug introduced by
It seems like self::parse_email($email) can also be of type array; however, parameter $hostname of Validate\Email::getMXrecords() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

305
        $mxs = $this->getMXrecords(/** @scrutinizer ignore-type */ self::parse_email($email)); 
Loading history...
306
        $timeout = ceil($this->max_connection_timeout / count($mxs)); 
307
        foreach ($mxs as $host) { 
308
            /** 
309
             * suppress error output from stream socket client... 
310
             * Thanks Michael. 
311
             */ 
312
            $this->stream = @stream_socket_client("tcp://" . $host . ":" . $this->port, $errno, $errstr, $timeout); 
313
            if ($this->stream === FALSE) { 
314
                if ($errno == 0) { 
315
                    $this->set_error("Problem initializing the socket"); 
316
                    $this->edebug($this->ErrorInfo); 
317
                    if ($this->exceptions) { 
318
                        throw new Exception($this->ErrorInfo); 
319
                    } 
320
                    return FALSE; 
321
                } else { 
322
                    $this->edebug($host . ":" . $errstr); 
323
                } 
324
            } else { 
325
                stream_set_timeout($this->stream, $this->stream_timeout); 
326
                stream_set_blocking($this->stream, 1); 
327
328
                if ($this->_streamCode($this->_streamResponse()) == '220') { 
329
                    $this->edebug("Connection success {$host}"); 
330
                    break; 
331
                } else { 
332
                    fclose($this->stream); 
333
                    $this->stream = FALSE; 
334
                } 
335
            } 
336
        } 
337
338
        if ($this->stream === FALSE) { 
339
            $this->set_error("All connection fails"); 
340
            $this->edebug($this->ErrorInfo); 
341
            if ($this->exceptions) { 
342
                throw new Exception($this->ErrorInfo); 
343
            } 
344
            return FALSE; 
345
        } 
346
347
        $this->_streamQuery("HELO " . self::parse_email($this->from)); 
0 ignored issues
show
Bug introduced by
Are you sure self::parse_email($this->from) of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

347
        $this->_streamQuery("HELO " . /** @scrutinizer ignore-type */ self::parse_email($this->from)); 
Loading history...
348
        $this->_streamResponse(); 
349
        $this->_streamQuery("MAIL FROM: <{$this->from}>"); 
350
        $this->_streamResponse(); 
351
        $this->_streamQuery("RCPT TO: <{$email}>"); 
352
        $code = $this->_streamCode($this->_streamResponse()); 
353
        $this->_streamResponse(); 
354
        $this->_streamQuery("RSET"); 
355
        $this->_streamResponse();
356
        $code2 = $this->_streamCode($this->_streamResponse()); 
357
        $this->_streamQuery("QUIT"); 
358
        fclose($this->stream); 
359
        
360
        $code = !empty($code2)?$code2:$code;
361
        switch ($code) { 
362
            case '250': 
363
            /** 
364
             * http://www.ietf.org/rfc/rfc0821.txt 
365
             * 250 Requested mail action okay, completed 
366
             * email address was accepted 
367
             */ 
368
            case '450': 
369
            case '451': 
370
            case '452': 
371
                /** 
372
                 * http://www.ietf.org/rfc/rfc0821.txt 
373
                 * 450 Requested action not taken: the remote mail server 
374
                 * does not want to accept mail from your server for 
375
                 * some reason (IP address, blacklisting, etc..) 
376
                 * Thanks Nicht Lieb. 
377
                 * 451 Requested action aborted: local error in processing 
378
                 * 452 Requested action not taken: insufficient system storage 
379
                 * email address was greylisted (or some temporary error occured on the MTA) 
380
                 * i believe that e-mail exists 
381
                 */ 
382
                return TRUE;
383
            case '550':
384
                return FALSE; 
385
            default : 
386
                return FALSE; 
387
        } 
388
    } 
389
390
    /** 
391
     * writes the contents of string to the file stream pointed to by handle 
392
     * If an error occurs, returns FALSE. 
393
     * @access protected 
394
     * @param string $string The string that is to be written 
395
     * @return string Returns a result code, as an integer. 
396
     */ 
397
    protected function _streamQuery($query) { 
398
        $this->edebug($query); 
399
        return stream_socket_sendto($this->stream, $query . self::CRLF); 
0 ignored issues
show
Bug introduced by
$this->stream of type boolean is incompatible with the type resource expected by parameter $socket of stream_socket_sendto(). ( Ignorable by Annotation )

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

399
        return stream_socket_sendto(/** @scrutinizer ignore-type */ $this->stream, $query . self::CRLF); 
Loading history...
400
    } 
401
402
    /** 
403
     * Reads all the line long the answer and analyze it. 
404
     * If an error occurs, returns FALSE 
405
     * @access protected 
406
     * @return string Response 
407
     */ 
408
    protected function _streamResponse($timed = 0) { 
409
        $reply = stream_get_line($this->stream, 1); 
0 ignored issues
show
Bug introduced by
$this->stream of type boolean is incompatible with the type resource expected by parameter $handle of stream_get_line(). ( Ignorable by Annotation )

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

409
        $reply = stream_get_line(/** @scrutinizer ignore-type */ $this->stream, 1); 
Loading history...
410
        $status = stream_get_meta_data($this->stream); 
0 ignored issues
show
Bug introduced by
$this->stream of type boolean is incompatible with the type resource expected by parameter $stream of stream_get_meta_data(). ( Ignorable by Annotation )

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

410
        $status = stream_get_meta_data(/** @scrutinizer ignore-type */ $this->stream); 
Loading history...
411
412
        if (!empty($status['timed_out'])) { 
413
            $this->edebug("Timed out while waiting for data! (timeout {$this->stream_timeout} seconds)"); 
414
        } 
415
416
        if ($reply === FALSE && $status['timed_out'] && $timed < $this->stream_timeout_wait) { 
417
            return $this->_streamResponse($timed + $this->stream_timeout); 
418
        } 
419
420
421
        if ($reply !== FALSE && $status['unread_bytes'] > 0) { 
422
            $reply .= stream_get_line($this->stream, $status['unread_bytes'], self::CRLF); 
423
        } 
424
        $this->edebug($reply); 
425
        return $reply; 
426
    } 
427
428
    /** 
429
     * Get Response code from Response 
430
     * @param string $str 
431
     * @return string 
432
     */ 
433
    protected function _streamCode($str) { 
434
        preg_match('/^(?<code>[0-9]{3})(\s|-)(.*)$/ims', $str, $matches); 
435
        $code = isset($matches['code']) ? $matches['code'] : false; 
436
        return $code; 
0 ignored issues
show
Bug Best Practice introduced by
The expression return $code could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
437
    } 
438
439
}