1 | <?php |
||||
2 | |||||
3 | namespace Validate; |
||||
4 | |||||
5 | use Exception; |
||||
6 | use Validate\Traits\BlockStringTrait; |
||||
7 | use Validate\Traits\GetDataTrait; |
||||
8 | |||||
9 | class Email implements \Validate\Contracts\Validate |
||||
10 | { |
||||
11 | use BlockStringTrait, GetDataTrait; |
||||
12 | |||||
13 | protected $stream = false; |
||||
14 | |||||
15 | /** |
||||
16 | * SMTP port number |
||||
17 | * |
||||
18 | * @var int |
||||
19 | */ |
||||
20 | protected $port = 25; |
||||
21 | |||||
22 | /** |
||||
23 | * Email address for request |
||||
24 | * |
||||
25 | * @var string |
||||
26 | */ |
||||
27 | protected $from = 'root@localhost'; |
||||
28 | |||||
29 | /** |
||||
30 | * The connection timeout, in seconds. |
||||
31 | * |
||||
32 | * @var int |
||||
33 | */ |
||||
34 | protected $max_connection_timeout = 30; |
||||
35 | |||||
36 | /** |
||||
37 | * Timeout value on stream, in seconds. |
||||
38 | * |
||||
39 | * @var int |
||||
40 | */ |
||||
41 | protected $stream_timeout = 5; |
||||
42 | |||||
43 | /** |
||||
44 | * Wait timeout on stream, in seconds. |
||||
45 | * * 0 - not wait |
||||
46 | * |
||||
47 | * @var int |
||||
48 | */ |
||||
49 | protected $stream_timeout_wait = 0; |
||||
50 | |||||
51 | /** |
||||
52 | * Whether to throw exceptions for errors. |
||||
53 | * |
||||
54 | * @type boolean |
||||
55 | * @access protected |
||||
56 | */ |
||||
57 | protected $exceptions = false; |
||||
58 | |||||
59 | /** |
||||
60 | * The number of errors encountered. |
||||
61 | * |
||||
62 | * @type integer |
||||
63 | * @access protected |
||||
64 | */ |
||||
65 | protected $error_count = 0; |
||||
66 | |||||
67 | /** |
||||
68 | * class debug output mode. |
||||
69 | * |
||||
70 | * @type boolean |
||||
71 | */ |
||||
72 | public $Debug = false; |
||||
73 | |||||
74 | /** |
||||
75 | * How to handle debug output. |
||||
76 | * Options: |
||||
77 | * * `echo` Output plain-text as-is, appropriate for CLI |
||||
78 | * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output |
||||
79 | * * `log` Output to error log as configured in php.ini |
||||
80 | * |
||||
81 | * @type string |
||||
82 | */ |
||||
83 | public $Debugoutput = 'echo'; |
||||
84 | |||||
85 | /** |
||||
86 | * SMTP RFC standard line ending. |
||||
87 | */ |
||||
88 | const CRLF = "\r\n"; |
||||
89 | |||||
90 | /** |
||||
91 | * Holds the most recent error message. |
||||
92 | * |
||||
93 | * @type string |
||||
94 | */ |
||||
95 | public $ErrorInfo = ''; |
||||
96 | |||||
97 | /** |
||||
98 | * Constructor. |
||||
99 | * |
||||
100 | * @param boolean $exceptions Should we throw external exceptions? |
||||
101 | */ |
||||
102 | public function __construct($exceptions = false) |
||||
103 | { |
||||
104 | $this->exceptions = (boolean) $exceptions; |
||||
105 | } |
||||
106 | |||||
107 | public static function isSame(string $to, string $from) |
||||
108 | { |
||||
109 | return (self::toDatabase($to)===self::toDatabase($from)); |
||||
110 | } |
||||
111 | |||||
112 | /** |
||||
113 | * Validate email address. |
||||
114 | * |
||||
115 | * @param string $email |
||||
116 | * @return boolean True if valid. |
||||
117 | */ |
||||
118 | public static function validate(string $email): boolean |
||||
0 ignored issues
–
show
|
|||||
119 | { |
||||
120 | if ((boolean) filter_var($email, FILTER_VALIDATE_EMAIL)===false) { |
||||
121 | return false; |
||||
0 ignored issues
–
show
|
|||||
122 | } |
||||
123 | |||||
124 | $emailAddresse = explode("@", trim($email)); |
||||
125 | |||||
126 | |||||
127 | if (self::foundInMultiplesArrays( |
||||
128 | [ |
||||
129 | [ |
||||
130 | $emailAddresse[0], |
||||
131 | self::getListFromFile('black-names') |
||||
132 | ], |
||||
133 | [ |
||||
134 | $emailAddresse[0], |
||||
135 | self::getListFromFile('black-first-names') |
||||
136 | ], |
||||
137 | ] |
||||
138 | ) |
||||
139 | ) { |
||||
140 | return false; |
||||
0 ignored issues
–
show
|
|||||
141 | } |
||||
142 | |||||
143 | if (!checkdnsrr($emailAddresse[1], "MX")) { |
||||
144 | return false; |
||||
0 ignored issues
–
show
|
|||||
145 | } |
||||
146 | |||||
147 | return true; |
||||
0 ignored issues
–
show
|
|||||
148 | } |
||||
149 | |||||
150 | /** |
||||
151 | * Email to Database |
||||
152 | * |
||||
153 | * @param string $email |
||||
154 | * @return string Email |
||||
155 | */ |
||||
156 | public static function toDatabase(string $email): string |
||||
157 | { |
||||
158 | return $email; |
||||
159 | } |
||||
160 | |||||
161 | /** |
||||
162 | * Email to User |
||||
163 | * |
||||
164 | * @param string $email |
||||
165 | * @return string Email |
||||
166 | */ |
||||
167 | public static function toUser(string $email): string |
||||
168 | { |
||||
169 | return $email; |
||||
170 | } |
||||
171 | |||||
172 | /** |
||||
173 | * Break Email |
||||
174 | * |
||||
175 | * @param string $email |
||||
176 | * @return array Email |
||||
177 | */ |
||||
178 | public static function break(string $email): array |
||||
179 | { |
||||
180 | $email = explode('@', $email); |
||||
181 | $data['address'] = $email[0]; |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
182 | $data['domain'] = $email[1]; |
||||
183 | return $data; |
||||
184 | } |
||||
185 | |||||
186 | /** |
||||
187 | * Set email address for SMTP request |
||||
188 | * |
||||
189 | * @param string $email Email address |
||||
190 | * @return void |
||||
191 | */ |
||||
192 | public function setEmailFrom(string $email): void |
||||
193 | { |
||||
194 | if (!self::validate($email)) { |
||||
195 | $this->set_error('Invalid address : ' . $email); |
||||
196 | $this->edebug($this->ErrorInfo); |
||||
197 | if ($this->exceptions) { |
||||
198 | throw new Exception($this->ErrorInfo); |
||||
199 | } |
||||
200 | } |
||||
201 | $this->from = $email; |
||||
202 | } |
||||
203 | |||||
204 | /** |
||||
205 | * Set connection timeout, in seconds. |
||||
206 | * |
||||
207 | * @param int $seconds |
||||
208 | */ |
||||
209 | public function setConnectionTimeout($seconds): void |
||||
210 | { |
||||
211 | if ($seconds > 0) { |
||||
212 | $this->max_connection_timeout = (int) $seconds; |
||||
213 | } |
||||
214 | } |
||||
215 | |||||
216 | /** |
||||
217 | * Sets the timeout value on stream, expressed in the seconds |
||||
218 | * |
||||
219 | * @param int $seconds |
||||
220 | */ |
||||
221 | public function setStreamTimeout($seconds): void |
||||
222 | { |
||||
223 | if ($seconds > 0) { |
||||
224 | $this->stream_timeout = (int) $seconds; |
||||
225 | } |
||||
226 | } |
||||
227 | |||||
228 | public function setStreamTimeoutWait($seconds): void |
||||
229 | { |
||||
230 | if ($seconds >= 0) { |
||||
231 | $this->stream_timeout_wait = (int) $seconds; |
||||
232 | } |
||||
233 | } |
||||
234 | |||||
235 | /** |
||||
236 | * Get array of MX records for host. Sort by weight information. |
||||
237 | * |
||||
238 | * @param string $hostname The Internet host name. |
||||
239 | * @return array Array of the MX records found. |
||||
240 | */ |
||||
241 | public function getMXrecords(string $hostname): array |
||||
242 | { |
||||
243 | $mxhosts = array(); |
||||
244 | $mxweights = array(); |
||||
245 | if (getmxrr($hostname, $mxhosts, $mxweights) === false) { |
||||
246 | $this->set_error('MX records not found or an error occurred'); |
||||
247 | $this->edebug($this->ErrorInfo); |
||||
248 | } else { |
||||
249 | array_multisort($mxweights, $mxhosts); |
||||
250 | } |
||||
251 | /** |
||||
252 | * Add A-record as last chance (e.g. if no MX record is there). |
||||
253 | * Thanks Nicht Lieb. |
||||
254 | * |
||||
255 | * @link http://www.faqs.org/rfcs/rfc2821.html RFC 2821 - Simple Mail Transfer Protocol |
||||
256 | */ |
||||
257 | if (empty($mxhosts)) { |
||||
258 | $mxhosts[] = $hostname; |
||||
259 | } |
||||
260 | return $mxhosts; |
||||
261 | } |
||||
262 | |||||
263 | /** |
||||
264 | * Parses input string to array(0=>user, 1=>domain) |
||||
265 | * |
||||
266 | * @param string $email |
||||
267 | * @param boolean $only_domain |
||||
268 | * @return string|array |
||||
269 | * @access private |
||||
270 | */ |
||||
271 | private static function parse_email(string $email, boolean $only_domain = true) |
||||
272 | { |
||||
273 | sscanf($email, "%[^@]@%s", $user, $domain); |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
274 | return ($only_domain) ? $domain : array($user, $domain); |
||||
275 | } |
||||
276 | |||||
277 | /** |
||||
278 | * Add an error message to the error container. |
||||
279 | * |
||||
280 | * @access protected |
||||
281 | * @param string $msg |
||||
282 | * @return void |
||||
283 | */ |
||||
284 | protected function set_error($msg) |
||||
285 | { |
||||
286 | $this->error_count++; |
||||
287 | $this->ErrorInfo = $msg; |
||||
288 | } |
||||
289 | |||||
290 | /** |
||||
291 | * Check if an error occurred. |
||||
292 | * |
||||
293 | * @access public |
||||
294 | * @return boolean True if an error did occur. |
||||
295 | */ |
||||
296 | public function isError(): boolean |
||||
297 | { |
||||
298 | return ($this->error_count > 0); |
||||
0 ignored issues
–
show
|
|||||
299 | } |
||||
300 | |||||
301 | /** |
||||
302 | * Output debugging info |
||||
303 | * Only generates output if debug output is enabled |
||||
304 | * |
||||
305 | * @see verifyEmail::$Debugoutput |
||||
306 | * @see verifyEmail::$Debug |
||||
307 | * @param string $str |
||||
308 | */ |
||||
309 | protected function edebug($str) |
||||
310 | { |
||||
311 | if (!$this->Debug) { |
||||
312 | return; |
||||
313 | } |
||||
314 | switch ($this->Debugoutput) { |
||||
315 | case 'log': |
||||
316 | //Don't output, just log |
||||
317 | error_log($str); |
||||
318 | break; |
||||
319 | case 'html': |
||||
320 | //Cleans up output a bit for a better looking, HTML-safe output |
||||
321 | echo htmlentities( |
||||
322 | preg_replace('/[\r\n]+/', '', $str), |
||||
323 | ENT_QUOTES, |
||||
324 | 'UTF-8' |
||||
325 | ) |
||||
326 | . "<br>\n"; |
||||
327 | break; |
||||
328 | case 'echo': |
||||
329 | default: |
||||
330 | //Normalize line breaks |
||||
331 | $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); |
||||
332 | echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( |
||||
333 | "\n", |
||||
334 | "\n \t ", |
||||
335 | trim($str) |
||||
336 | ) . "\n"; |
||||
337 | } |
||||
338 | } |
||||
339 | |||||
340 | /** |
||||
341 | * Validate email |
||||
342 | * |
||||
343 | * @param string $email Email address |
||||
344 | * @return boolean True if the valid email also exist |
||||
345 | */ |
||||
346 | public function check(string $email): boolean |
||||
347 | { |
||||
348 | $result = false; |
||||
0 ignored issues
–
show
|
|||||
349 | |||||
350 | if (!self::validate($email)) { |
||||
351 | $this->set_error("{$email} incorrect e-mail"); |
||||
352 | $this->edebug($this->ErrorInfo); |
||||
353 | if ($this->exceptions) { |
||||
354 | throw new Exception($this->ErrorInfo); |
||||
355 | } |
||||
356 | return false; |
||||
0 ignored issues
–
show
|
|||||
357 | } |
||||
358 | $this->error_count = 0; // Reset errors |
||||
359 | $this->stream = false; |
||||
360 | |||||
361 | $mxs = $this->getMXrecords(self::parse_email($email)); |
||||
0 ignored issues
–
show
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
![]() |
|||||
362 | $timeout = ceil($this->max_connection_timeout / count($mxs)); |
||||
363 | foreach ($mxs as $host) { |
||||
364 | /** |
||||
365 | * suppress error output from stream socket client... |
||||
366 | * Thanks Michael. |
||||
367 | */ |
||||
368 | $this->stream = @stream_socket_client("tcp://" . $host . ":" . $this->port, $errno, $errstr, $timeout); |
||||
369 | if ($this->stream === false) { |
||||
370 | if ($errno == 0) { |
||||
371 | $this->set_error("Problem initializing the socket"); |
||||
372 | $this->edebug($this->ErrorInfo); |
||||
373 | if ($this->exceptions) { |
||||
374 | throw new Exception($this->ErrorInfo); |
||||
375 | } |
||||
376 | return false; |
||||
0 ignored issues
–
show
|
|||||
377 | } else { |
||||
378 | $this->edebug($host . ":" . $errstr); |
||||
379 | } |
||||
380 | } else { |
||||
381 | stream_set_timeout($this->stream, $this->stream_timeout); |
||||
382 | stream_set_blocking($this->stream, 1); |
||||
383 | |||||
384 | if ($this->_streamCode($this->_streamResponse()) == '220') { |
||||
385 | $this->edebug("Connection success {$host}"); |
||||
386 | break; |
||||
387 | } else { |
||||
388 | fclose($this->stream); |
||||
389 | $this->stream = false; |
||||
390 | } |
||||
391 | } |
||||
392 | } |
||||
393 | |||||
394 | if ($this->stream === false) { |
||||
395 | $this->set_error("All connection fails"); |
||||
396 | $this->edebug($this->ErrorInfo); |
||||
397 | if ($this->exceptions) { |
||||
398 | throw new Exception($this->ErrorInfo); |
||||
399 | } |
||||
400 | return false; |
||||
0 ignored issues
–
show
|
|||||
401 | } |
||||
402 | |||||
403 | $this->_streamQuery("HELO " . self::parse_email($this->from)); |
||||
0 ignored issues
–
show
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
![]() |
|||||
404 | $this->_streamResponse(); |
||||
405 | $this->_streamQuery("MAIL FROM: <{$this->from}>"); |
||||
406 | $this->_streamResponse(); |
||||
407 | $this->_streamQuery("RCPT TO: <{$email}>"); |
||||
408 | $code = $this->_streamCode($this->_streamResponse()); |
||||
409 | $this->_streamResponse(); |
||||
410 | $this->_streamQuery("RSET"); |
||||
411 | $this->_streamResponse(); |
||||
412 | $code2 = $this->_streamCode($this->_streamResponse()); |
||||
413 | $this->_streamQuery("QUIT"); |
||||
414 | fclose($this->stream); |
||||
415 | |||||
416 | $code = !empty($code2)?$code2:$code; |
||||
417 | switch ($code) { |
||||
418 | case '250': |
||||
419 | /** |
||||
420 | * http://www.ietf.org/rfc/rfc0821.txt |
||||
421 | * 250 Requested mail action okay, completed |
||||
422 | * email address was accepted |
||||
423 | */ |
||||
424 | // no break |
||||
425 | case '450': |
||||
426 | case '451': |
||||
427 | case '452': |
||||
428 | /** |
||||
429 | * http://www.ietf.org/rfc/rfc0821.txt |
||||
430 | * 450 Requested action not taken: the remote mail server |
||||
431 | * does not want to accept mail from your server for |
||||
432 | * some reason (IP address, blacklisting, etc..) |
||||
433 | * Thanks Nicht Lieb. |
||||
434 | * 451 Requested action aborted: local error in processing |
||||
435 | * 452 Requested action not taken: insufficient system storage |
||||
436 | * email address was greylisted (or some temporary error occured on the MTA) |
||||
437 | * i believe that e-mail exists |
||||
438 | */ |
||||
439 | return true; |
||||
0 ignored issues
–
show
|
|||||
440 | case '550': |
||||
441 | return false; |
||||
0 ignored issues
–
show
|
|||||
442 | default: |
||||
443 | return false; |
||||
0 ignored issues
–
show
|
|||||
444 | } |
||||
445 | } |
||||
446 | |||||
447 | /** |
||||
448 | * writes the contents of string to the file stream pointed to by handle |
||||
449 | * If an error occurs, returns FALSE. |
||||
450 | * |
||||
451 | * @access protected |
||||
452 | * @param string $string The string that is to be written |
||||
453 | * @return string Returns a result code, as an integer. |
||||
454 | */ |
||||
455 | protected function _streamQuery(string $query) |
||||
456 | { |
||||
457 | $this->edebug($query); |
||||
458 | return stream_socket_sendto($this->stream, $query . self::CRLF); |
||||
0 ignored issues
–
show
$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
![]() |
|||||
459 | } |
||||
460 | |||||
461 | /** |
||||
462 | * Reads all the line long the answer and analyze it. |
||||
463 | * If an error occurs, returns FALSE |
||||
464 | * |
||||
465 | * @access protected |
||||
466 | * @return string Response |
||||
467 | */ |
||||
468 | protected function _streamResponse($timed = 0) |
||||
469 | { |
||||
470 | $reply = stream_get_line($this->stream, 1); |
||||
0 ignored issues
–
show
$this->stream of type boolean is incompatible with the type resource expected by parameter $stream of stream_get_line() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
471 | $status = stream_get_meta_data($this->stream); |
||||
0 ignored issues
–
show
$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
![]() |
|||||
472 | |||||
473 | if (!empty($status['timed_out'])) { |
||||
474 | $this->edebug("Timed out while waiting for data! (timeout {$this->stream_timeout} seconds)"); |
||||
475 | } |
||||
476 | |||||
477 | if ($reply === false && $status['timed_out'] && $timed < $this->stream_timeout_wait) { |
||||
478 | return $this->_streamResponse($timed + $this->stream_timeout); |
||||
479 | } |
||||
480 | |||||
481 | |||||
482 | if ($reply !== false && $status['unread_bytes'] > 0) { |
||||
483 | $reply .= stream_get_line($this->stream, $status['unread_bytes'], self::CRLF); |
||||
484 | } |
||||
485 | $this->edebug($reply); |
||||
486 | return $reply; |
||||
487 | } |
||||
488 | |||||
489 | /** |
||||
490 | * Get Response code from Response |
||||
491 | * |
||||
492 | * @param string $str |
||||
493 | * @return string |
||||
494 | */ |
||||
495 | protected function _streamCode(string $str): string |
||||
496 | { |
||||
497 | preg_match('/^(?<code>[0-9]{3})(\s|-)(.*)$/ims', $str, $matches); |
||||
498 | $code = isset($matches['code']) ? $matches['code'] : false; |
||||
499 | return $code; |
||||
0 ignored issues
–
show
|
|||||
500 | } |
||||
501 | } |
||||
502 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths