SmtpChecker::setFrom()   A
last analyzed

Complexity

Conditions 3
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 10
Ratio 100 %

Importance

Changes 0
Metric Value
dl 10
loc 10
c 0
b 0
f 0
rs 9.4285
cc 3
eloc 7
nc 1
nop 0
1
<?php
2
3
/*
4
 * This file is part of EmailChecker.
5
 *
6
 * (c) Corrado Ronci <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace sorciulus\EmailChecker;
13
14
use sorciulus\EmailChecker\Exception\SmtpCheckerException;
15
use sorciulus\EmailChecker\MxChecker;
16
use \Graze\TelnetClient\Exception\TelnetException;
0 ignored issues
show
Bug introduced by
The type Graze\TelnetClient\Exception\TelnetException was not found. Maybe you did not declare it correctly or list all dependencies?

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:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use \miyahan\network\Telnet;
18
/**
19
* This Class check the email address is valid 
20
* through commands executed on the SMTP Server
21
*/
22
class SmtpChecker implements SmtpInterface
23
{
24
	/**
25
	 * Instance of Telnet class
26
	 * @var object Telnet
27
	 */
28
	private $client;
29
30
	/**
31
	 * The domain of SMTP Server
32
	 * @var string
33
	 */
34
	private $domain;
35
36
	/**
37
	 * Response Code SMTP Server
38
	 * @var integer
39
	 */
40
	private $code;
41
42
	/**
43
	 * The given email is valid or not.
44
	 * @var boolean
45
	 */
46
	private $isValid;
47
48
	/**
49
	 * List of command and response executed
50
	 * @var array
51
	 */
52
	private $debug = [];
53
54
	/**
55
	 * The sender of SMTP check email
56
	 * @var string
57
	 */
58
	private $sender = "[email protected]";
59
60
	// some smtp response codes
61
    const SMTP_CONNECT_SUCCESS = 220;
62
    const SMTP_QUIT_SUCCESS = 221;
63
    const SMTP_GENERIC_SUCCESS = 250;
64
    const SMTP_USER_NOT_LOCAL = 251;
65
    const SMTP_CANNOT_VRFY = 252;
66
    const SMTP_SERVICE_UNAVAILABLE = 421;
67
    // 450  Requested mail action not taken: mailbox unavailable (e.g.,
68
    // mailbox busy or temporarily blocked for policy reasons)
69
    const SMTP_MAIL_ACTION_NOT_TAKEN = 450;
70
    // 451  Requested action aborted: local error in processing
71
    const SMTP_MAIL_ACTION_ABORTED = 451;
72
    // 452  Requested action not taken: insufficient system storage
73
    const SMTP_REQUESTED_ACTION_NOT_TAKEN = 452;
74
    // 500  Syntax error (may be due to a denied command)
75
    const SMTP_SYNTAX_ERROR = 500;
76
    // 502  Comment not implemented
77
    const SMTP_NOT_IMPLEMENTED = 502;
78
    // 503  Bad sequence of commands (may be due to a denied command)
79
    const SMTP_BAD_SEQUENCE = 503;
80
    // 550  Requested action not taken: mailbox unavailable (e.g., mailbox
81
    // not found, no access, or command rejected for policy reasons)
82
    const SMTP_MBOX_UNAVAILABLE = 550;
83
    const SMTP_USER_NOT_LOCAL_FORWARD = 551;
84
    // 552  Requested mail action aborted: exceeded storage allocation
85
    const SMTP_EXCEEDED_STORAGE_ALLOCAION = 552;
86
    //553  Requested action not taken: mailbox name not allowed (e.g.,
87
    //mailbox syntax incorrect)
88
    const SMTP_MBOX_NAME_NOT_ALLOWED = 553;
89
    // 554  Seen this from hotmail MTAs, in response to RSET :(
90
    const SMTP_TRANSACTION_FAILED = 554;
91
92
    /**    
93
     * @param array $domains List of SMTP domains 
94
     * @param boolean $sender Set sender of STMP command 
95
     * @param integer $timeout Timeout of the stream
96
     *
97
     * @throws SmtpCheckerException when all domain not valid
98
     */
99
	function __construct(MxInterface $domains, $sender = "", int $timeout = 10)
100
	{						
101
		foreach ($domains->getRecordMx() as $domain) {
102
			try {				
103
				$this->debug  = ["Connect to : ".$domain["target"]." on port 25"];
104
				$this->client = new Telnet($domain["target"], 25, $timeout);								
105
				$this->domain = $domain;
106
				break;
107
			} catch (\Exception $ex) {							
108
				continue;
109
			}
110
		}
111
		if (empty($this->getDomain())) {
112
			throw new SmtpCheckerException("Error Processing Request ".$ex->getMessage());			
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ex seems to be defined by a foreach iteration on line 101. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
113
		}		
114
				
115
		if (!empty($sender)) {
116
			$this->setSender($sender);
117
		}
118
		$this->sayHello();
119
		$this->setFrom();		
120
	}
121
122
	/**
123
    * Sets the The sender of SMTP check email.
124
    *
125
    * @param string $sender the sender
126
    *
127
    * @return void
128
    */
129
    private function setSender($sender)
130
    {
131
        if (!filter_var($sender, FILTER_VALIDATE_EMAIL)) {
132
			throw new SmtpCheckerException("Email Sender not valid");		
133
		}
134
		$this->sender = $sender;				
135
		       		
136
    }
137
138
	/**
139
    * Gets the value of sender.
140
    *
141
    * @return string
142
    */
143
	private function getSender()
144
	{
145
		return $this->sender;
146
	}
147
148
	/**
149
    * Gets the value of domain.
150
    *
151
    * @return array
152
    */
153
	private function getDomain()
154
	{
155
		return $this->domain;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->domain returns the type string which is incompatible with the documented return type array.
Loading history...
156
	}
157
158
	/**
159
    * Gets the value of getDomain in object.
160
    *
161
    * @return object
162
    */
163
	private function getDomainObj()
164
	{
165
		return (object) $this->getDomain();
166
	}
167
168
	/**
169
	 * Send command HELO to SMTP Server
170
	 * 
171
	 * @return void
172
	 *
173
	 * @throws SmtpCheckerException when the SMTP server return error
174
	 */
175 View Code Duplication
	private function sayHello()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
176
	{
177
		$host = $this->getDomainObj()->host;
178
		try{				
179
			$status = $this->commandExec(sprintf("HELO %s", $host));				
180
			if (!in_array(self::SMTP_GENERIC_SUCCESS, $status)) {
181
				$this->disconnect();
182
				throw new SmtpCheckerException("SMTP Status unexpected: ".implode(",", $status), $this->getDebug());	 
183
			}
184
		}catch (\Exception $ex) {			
185
			throw new SmtpCheckerException("Error Processing Request ".$ex->getMessage(), $this->getDebug());							
186
		}
187
		
188
	}
189
190
	/**
191
	 * Set MAIL FROM to SMTP Server
192
	 * 
193
	 * @return void
194
	 *
195
	 * @throws SmtpCheckerException when the SMTP server return error
196
	 */
197 View Code Duplication
	private function setFrom()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
198
	{		
199
		try{
200
			$status = $this->commandExec(sprintf("MAIL FROM:<%s>", $this->getSender()));				
201
			if (!in_array(self::SMTP_GENERIC_SUCCESS, $status)) {
202
				$this->disconnect();
203
				throw new SmtpCheckerException("SMTP Status unexpected: ".implode(",", $status), $this->getDebug());	 
204
			}
205
		}catch (\Exception $ex) {					
206
			throw new SmtpCheckerException("Error Processing Request setFrom : ".$ex->getMessage(), $this->getDebug());
207
		}
208
		
209
	}
210
211
	/**
212
	 * Wrapper to Telnet Client to Execute SMTP command, will return the status code
213
	 * 
214
	 * @param  string $command   command execute
215
	 * @return array  SMPT status
216
	 */
217
	private function commandExec($command)
218
	{		
219
		$this->debug[] = $command;	
220
		$exec = $this->client->exec($command);	
221
		return $this->getResponseStatus($exec);		
222
	}
223
224
	/**
225
	 * Get response status code of previous SMTP request 
226
	 * 
227
	 * @return array $status The status of SMTP request
228
	 *
229
	 * @throws SmtpCheckerException when the SMTP server return unknown error
230
	 */
231
	private function getResponseStatus($output)
232
	{
233
		$status = [];
234
		$strout = explode("\n", $output);			
235
		if (empty($strout)) {
236
			$strout = [$output];
237
		}				
238
		foreach ($strout as $str) {
239
			$this->debug[] = $str;
240
			if(preg_match('/^[0-9]{3}/', $str, $match)) {				
241
				$status[] = current($match);
242
			}
243
		}
244
		if(empty($status)) {			
245
			throw new SmtpCheckerException("SMTP Status request Unknown, ". $output, $this->getDebug());			
246
		}
247
		return $status;
248
	}
249
250
251
	/**
252
	 * Close connection to SMTP Server
253
	 * 
254
	 * @return void
255
	 */
256
	private function disconnect()
257
	{
258
		$this->client->exec("quit");		
259
		$this->client->disconnect();
260
		$this->debug[] = "QUIT";
261
	}
262
263
	/**
264
	 *	Set RCPT command to SMTP server
265
	 * 
266
	 * @param string $email The email to validate
267
	 * @return self
268
	 *
269
	 * @throws SmtpCheckerException when the SMTP server return error
270
	 */
271
	public function validate($email)
272
	{
273
		try{			
274
			$command = sprintf("RCPT TO:<%s>", $email);
275
			$exec = $this->client->exec($command);
276
			$this->debug[] = $command;			
277
			$status = $this->getResponseStatus($exec);										
278
			$this->disconnect();
279
			$this->code = end($status);
280
			$this->isValid = false;
281
			if (in_array($this->getCode(), [self::SMTP_USER_NOT_LOCAL, self::SMTP_GENERIC_SUCCESS])) {
282
				$this->isValid = true;
283
			} 
284
			return $this;			
285
		}catch (\Exception $ex) {						
286
			throw new SmtpCheckerException("Error Processing Request Validate : ".$ex->getMessage(), $this->getDebug());
287
		}
288
	}
289
290
    /**
291
    * Gets the value of code.
292
    *
293
    * @return integer
294
    */
295
    public function getCode()
296
    {
297
        return (int) $this->code;
298
    } 
299
300
    /**
301
    * Gets the value of isValid.
302
    *
303
    * @return boolean
304
    */
305
    public function isValid()
306
    {
307
        return $this->isValid;
308
    }
309
310
    /**
311
    * Gets the value of debug.
312
    *
313
    * @return array
314
    */
315
    public function getDebug()
316
    {
317
        return $this->debug;
318
    } 
319
320
    /**
321
    * Gets the value of message.
322
    *
323
    * @return string
324
    */
325
    public function getMessage()
326
    {
327
        $code     = $this->getCode();
328
        $messages = [
329
        	self::SMTP_CONNECT_SUCCESS => "SMTP connect Success",
330
		    self::SMTP_QUIT_SUCCESS => "Quit Success",
331
		    self::SMTP_GENERIC_SUCCESS => "SMTP Generic Success",
332
		    self::SMTP_USER_NOT_LOCAL => "SMTP User not local",
333
		    self::SMTP_CANNOT_VRFY => "SMTP Cannot VRFY user",
334
		    self::SMTP_SERVICE_UNAVAILABLE => "SMTP Service not available",
335
		    self::SMTP_MAIL_ACTION_NOT_TAKEN => "SMTP mailbox busy or temporarily blocked for policy reasons",		    
336
		    self::SMTP_MAIL_ACTION_ABORTED => "SMTP Requested action aborted: local error in processing",		    
337
		    self::SMTP_REQUESTED_ACTION_NOT_TAKEN => "SMTP Requested action not taken: insufficient system storage",		    
338
		    self::SMTP_SYNTAX_ERROR => "SMTP Syntax error, command unrecognized",		    
339
		    self::SMTP_NOT_IMPLEMENTED => "SMTP Command not implemented",	    
340
		    self::SMTP_BAD_SEQUENCE => "SMTP Bad sequence of commands",
341
		    self::SMTP_MBOX_UNAVAILABLE => "SMTP Requested action not taken: mailbox unavailable",
342
		    self::SMTP_USER_NOT_LOCAL_FORWARD => "SMTP User not local",		    
343
		    self::SMTP_EXCEEDED_STORAGE_ALLOCAION => "SMTP Requested mail action aborted: exceeded storage allocation",		    
344
		    self::SMTP_MBOX_NAME_NOT_ALLOWED => "Requested action not taken: mailbox name not allowed",
345
		    self::SMTP_TRANSACTION_FAILED => "SMTP Transaction failed"
346
        ];
347
348
        return
349
        	(array_key_exists($code, $messages))
350
        	? $messages[$code]
351
        	: "Unknown message from SMTP";
352
    }
353
}