pictcode / lib / Cake / Network / Email / SmtpTransport.php @ f4a6dc2c
履歴 | 表示 | アノテート | ダウンロード (8.985 KB)
| 1 | 635eef61 | spyder1211 | <?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 | }  |