pictcode / lib / Cake / Network / Email / SmtpTransport.php @ 0b1b8047
履歴 | 表示 | アノテート | ダウンロード (8.985 KB)
1 |
<?php
|
---|---|
2 |
/**
|
3 |
* Send mail using SMTP protocol
|
4 |
*
|
5 |
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
6 |
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
7 |
*
|
8 |
* Licensed under The MIT License
|
9 |
* For full copyright and license information, please see the LICENSE.txt
|
10 |
* Redistributions of files must retain the above copyright notice.
|
11 |
*
|
12 |
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
13 |
* @link http://cakephp.org CakePHP(tm) Project
|
14 |
* @package Cake.Network.Email
|
15 |
* @since CakePHP(tm) v 2.0.0
|
16 |
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
17 |
*/
|
18 |
|
19 |
App::uses('CakeSocket', 'Network'); |
20 |
|
21 |
/**
|
22 |
* Send mail using SMTP protocol
|
23 |
*
|
24 |
* @package Cake.Network.Email
|
25 |
*/
|
26 |
class SmtpTransport extends AbstractTransport { |
27 |
|
28 |
/**
|
29 |
* Socket to SMTP server
|
30 |
*
|
31 |
* @var CakeSocket
|
32 |
*/
|
33 |
protected $_socket; |
34 |
|
35 |
/**
|
36 |
* CakeEmail
|
37 |
*
|
38 |
* @var CakeEmail
|
39 |
*/
|
40 |
protected $_cakeEmail; |
41 |
|
42 |
/**
|
43 |
* Content of email to return
|
44 |
*
|
45 |
* @var string
|
46 |
*/
|
47 |
protected $_content; |
48 |
|
49 |
/**
|
50 |
* The response of the last sent SMTP command.
|
51 |
*
|
52 |
* @var array
|
53 |
*/
|
54 |
protected $_lastResponse = array(); |
55 |
|
56 |
/**
|
57 |
* Returns the response of the last sent SMTP command.
|
58 |
*
|
59 |
* A response consists of one or more lines containing a response
|
60 |
* code and an optional response message text:
|
61 |
* ```
|
62 |
* array(
|
63 |
* array(
|
64 |
* 'code' => '250',
|
65 |
* 'message' => 'mail.example.com'
|
66 |
* ),
|
67 |
* array(
|
68 |
* 'code' => '250',
|
69 |
* 'message' => 'PIPELINING'
|
70 |
* ),
|
71 |
* array(
|
72 |
* 'code' => '250',
|
73 |
* 'message' => '8BITMIME'
|
74 |
* ),
|
75 |
* // etc...
|
76 |
* )
|
77 |
* ```
|
78 |
*
|
79 |
* @return array
|
80 |
*/
|
81 |
public function getLastResponse() { |
82 |
return $this->_lastResponse; |
83 |
} |
84 |
|
85 |
/**
|
86 |
* Send mail
|
87 |
*
|
88 |
* @param CakeEmail $email CakeEmail
|
89 |
* @return array
|
90 |
* @throws SocketException
|
91 |
*/
|
92 |
public function send(CakeEmail $email) { |
93 |
$this->_cakeEmail = $email; |
94 |
|
95 |
$this->_connect();
|
96 |
$this->_auth();
|
97 |
$this->_sendRcpt();
|
98 |
$this->_sendData();
|
99 |
$this->_disconnect();
|
100 |
|
101 |
return $this->_content; |
102 |
} |
103 |
|
104 |
/**
|
105 |
* Set the configuration
|
106 |
*
|
107 |
* @param array $config Configuration options.
|
108 |
* @return array Returns configs
|
109 |
*/
|
110 |
public function config($config = null) { |
111 |
if ($config === null) { |
112 |
return $this->_config; |
113 |
} |
114 |
$default = array( |
115 |
'host' => 'localhost', |
116 |
'port' => 25, |
117 |
'timeout' => 30, |
118 |
'username' => null, |
119 |
'password' => null, |
120 |
'client' => null, |
121 |
'tls' => false, |
122 |
'ssl_allow_self_signed' => false |
123 |
); |
124 |
$this->_config = array_merge($default, $this->_config, $config); |
125 |
return $this->_config; |
126 |
} |
127 |
|
128 |
/**
|
129 |
* Parses and stores the reponse lines in `'code' => 'message'` format.
|
130 |
*
|
131 |
* @param array $responseLines Response lines to parse.
|
132 |
* @return void
|
133 |
*/
|
134 |
protected function _bufferResponseLines(array $responseLines) { |
135 |
$response = array(); |
136 |
foreach ($responseLines as $responseLine) { |
137 |
if (preg_match('/^(\d{3})(?:[ -]+(.*))?$/', $responseLine, $match)) { |
138 |
$response[] = array( |
139 |
'code' => $match[1], |
140 |
'message' => isset($match[2]) ? $match[2] : null |
141 |
); |
142 |
} |
143 |
} |
144 |
$this->_lastResponse = array_merge($this->_lastResponse, $response); |
145 |
} |
146 |
|
147 |
/**
|
148 |
* Connect to SMTP Server
|
149 |
*
|
150 |
* @return void
|
151 |
* @throws SocketException
|
152 |
*/
|
153 |
protected function _connect() { |
154 |
$this->_generateSocket();
|
155 |
if (!$this->_socket->connect()) { |
156 |
throw new SocketException(__d('cake_dev', 'Unable to connect to SMTP server.')); |
157 |
} |
158 |
$this->_smtpSend(null, '220'); |
159 |
|
160 |
if (isset($this->_config['client'])) { |
161 |
$host = $this->_config['client']; |
162 |
} elseif ($httpHost = env('HTTP_HOST')) { |
163 |
list($host) = explode(':', $httpHost); |
164 |
} else {
|
165 |
$host = 'localhost'; |
166 |
} |
167 |
|
168 |
try {
|
169 |
$this->_smtpSend("EHLO {$host}", '250'); |
170 |
if ($this->_config['tls']) { |
171 |
$this->_smtpSend("STARTTLS", '220'); |
172 |
$this->_socket->enableCrypto('tls'); |
173 |
$this->_smtpSend("EHLO {$host}", '250'); |
174 |
} |
175 |
} catch (SocketException $e) { |
176 |
if ($this->_config['tls']) { |
177 |
throw new SocketException(__d('cake_dev', 'SMTP server did not accept the connection or trying to connect to non TLS SMTP server using TLS.')); |
178 |
} |
179 |
try {
|
180 |
$this->_smtpSend("HELO {$host}", '250'); |
181 |
} catch (SocketException $e2) { |
182 |
throw new SocketException(__d('cake_dev', 'SMTP server did not accept the connection.')); |
183 |
} |
184 |
} |
185 |
} |
186 |
|
187 |
/**
|
188 |
* Send authentication
|
189 |
*
|
190 |
* @return void
|
191 |
* @throws SocketException
|
192 |
*/
|
193 |
protected function _auth() { |
194 |
if (isset($this->_config['username']) && isset($this->_config['password'])) { |
195 |
$replyCode = $this->_smtpSend('AUTH LOGIN', '334|500|502|504'); |
196 |
if ($replyCode == '334') { |
197 |
try {
|
198 |
$this->_smtpSend(base64_encode($this->_config['username']), '334'); |
199 |
} catch (SocketException $e) { |
200 |
throw new SocketException(__d('cake_dev', 'SMTP server did not accept the username.')); |
201 |
} |
202 |
try {
|
203 |
$this->_smtpSend(base64_encode($this->_config['password']), '235'); |
204 |
} catch (SocketException $e) { |
205 |
throw new SocketException(__d('cake_dev', 'SMTP server did not accept the password.')); |
206 |
} |
207 |
} elseif ($replyCode == '504') { |
208 |
throw new SocketException(__d('cake_dev', 'SMTP authentication method not allowed, check if SMTP server requires TLS.')); |
209 |
} else {
|
210 |
throw new SocketException(__d('cake_dev', 'AUTH command not recognized or not implemented, SMTP server may not require authentication.')); |
211 |
} |
212 |
} |
213 |
} |
214 |
|
215 |
/**
|
216 |
* Prepares the `MAIL FROM` SMTP command.
|
217 |
*
|
218 |
* @param string $email The email address to send with the command.
|
219 |
* @return string
|
220 |
*/
|
221 |
protected function _prepareFromCmd($email) { |
222 |
return 'MAIL FROM:<' . $email . '>'; |
223 |
} |
224 |
|
225 |
/**
|
226 |
* Prepares the `RCPT TO` SMTP command.
|
227 |
*
|
228 |
* @param string $email The email address to send with the command.
|
229 |
* @return string
|
230 |
*/
|
231 |
protected function _prepareRcptCmd($email) { |
232 |
return 'RCPT TO:<' . $email . '>'; |
233 |
} |
234 |
|
235 |
/**
|
236 |
* Prepares the `from` email address.
|
237 |
*
|
238 |
* @return array
|
239 |
*/
|
240 |
protected function _prepareFromAddress() { |
241 |
$from = $this->_cakeEmail->returnPath(); |
242 |
if (empty($from)) { |
243 |
$from = $this->_cakeEmail->from(); |
244 |
} |
245 |
return $from; |
246 |
} |
247 |
|
248 |
/**
|
249 |
* Prepares the recipient email addresses.
|
250 |
*
|
251 |
* @return array
|
252 |
*/
|
253 |
protected function _prepareRecipientAddresses() { |
254 |
$to = $this->_cakeEmail->to(); |
255 |
$cc = $this->_cakeEmail->cc(); |
256 |
$bcc = $this->_cakeEmail->bcc(); |
257 |
return array_merge(array_keys($to), array_keys($cc), array_keys($bcc)); |
258 |
} |
259 |
|
260 |
/**
|
261 |
* Prepares the message headers.
|
262 |
*
|
263 |
* @return array
|
264 |
*/
|
265 |
protected function _prepareMessageHeaders() { |
266 |
return $this->_cakeEmail->getHeaders(array('from', 'sender', 'replyTo', 'readReceipt', 'to', 'cc', 'subject')); |
267 |
} |
268 |
|
269 |
/**
|
270 |
* Prepares the message body.
|
271 |
*
|
272 |
* @return string
|
273 |
*/
|
274 |
protected function _prepareMessage() { |
275 |
$lines = $this->_cakeEmail->message(); |
276 |
$messages = array(); |
277 |
foreach ($lines as $line) { |
278 |
if ((!empty($line)) && ($line[0] === '.')) { |
279 |
$messages[] = '.' . $line; |
280 |
} else {
|
281 |
$messages[] = $line; |
282 |
} |
283 |
} |
284 |
return implode("\r\n", $messages); |
285 |
} |
286 |
|
287 |
/**
|
288 |
* Send emails
|
289 |
*
|
290 |
* @return void
|
291 |
* @throws SocketException
|
292 |
*/
|
293 |
protected function _sendRcpt() { |
294 |
$from = $this->_prepareFromAddress(); |
295 |
$this->_smtpSend($this->_prepareFromCmd(key($from))); |
296 |
|
297 |
$emails = $this->_prepareRecipientAddresses(); |
298 |
foreach ($emails as $email) { |
299 |
$this->_smtpSend($this->_prepareRcptCmd($email)); |
300 |
} |
301 |
} |
302 |
|
303 |
/**
|
304 |
* Send Data
|
305 |
*
|
306 |
* @return void
|
307 |
* @throws SocketException
|
308 |
*/
|
309 |
protected function _sendData() { |
310 |
$this->_smtpSend('DATA', '354'); |
311 |
|
312 |
$headers = $this->_headersToString($this->_prepareMessageHeaders()); |
313 |
$message = $this->_prepareMessage(); |
314 |
|
315 |
$this->_smtpSend($headers . "\r\n\r\n" . $message . "\r\n\r\n\r\n."); |
316 |
$this->_content = array('headers' => $headers, 'message' => $message); |
317 |
} |
318 |
|
319 |
/**
|
320 |
* Disconnect
|
321 |
*
|
322 |
* @return void
|
323 |
* @throws SocketException
|
324 |
*/
|
325 |
protected function _disconnect() { |
326 |
$this->_smtpSend('QUIT', false); |
327 |
$this->_socket->disconnect();
|
328 |
} |
329 |
|
330 |
/**
|
331 |
* Helper method to generate socket
|
332 |
*
|
333 |
* @return void
|
334 |
* @throws SocketException
|
335 |
*/
|
336 |
protected function _generateSocket() { |
337 |
$this->_socket = new CakeSocket($this->_config); |
338 |
} |
339 |
|
340 |
/**
|
341 |
* Protected method for sending data to SMTP connection
|
342 |
*
|
343 |
* @param string|null $data Data to be sent to SMTP server
|
344 |
* @param string|bool $checkCode Code to check for in server response, false to skip
|
345 |
* @return string|null The matched code, or null if nothing matched
|
346 |
* @throws SocketException
|
347 |
*/
|
348 |
protected function _smtpSend($data, $checkCode = '250') { |
349 |
$this->_lastResponse = array(); |
350 |
|
351 |
if ($data !== null) { |
352 |
$this->_socket->write($data . "\r\n"); |
353 |
} |
354 |
while ($checkCode !== false) { |
355 |
$response = ''; |
356 |
$startTime = time(); |
357 |
while (substr($response, -2) !== "\r\n" && ((time() - $startTime) < $this->_config['timeout'])) { |
358 |
$response .= $this->_socket->read(); |
359 |
} |
360 |
if (substr($response, -2) !== "\r\n") { |
361 |
throw new SocketException(__d('cake_dev', 'SMTP timeout.')); |
362 |
} |
363 |
$responseLines = explode("\r\n", rtrim($response, "\r\n")); |
364 |
$response = end($responseLines); |
365 |
|
366 |
$this->_bufferResponseLines($responseLines); |
367 |
|
368 |
if (preg_match('/^(' . $checkCode . ')(.)/', $response, $code)) { |
369 |
if ($code[2] === '-') { |
370 |
continue;
|
371 |
} |
372 |
return $code[1]; |
373 |
} |
374 |
throw new SocketException(__d('cake_dev', 'SMTP Error: %s', $response)); |
375 |
} |
376 |
} |
377 |
|
378 |
} |