統計
| ブランチ: | リビジョン:

pictcode / lib / Cake / Network / Email / SmtpTransport.php @ 635eef61

履歴 | 表示 | アノテート | ダウンロード (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
}