Completed
Push — 5.x ( 2b0825...7df5e8 )
by Lars
04:49
created

Swift_Transport_AbstractSmtpTransport::ping()   A

Complexity

Conditions 4
Paths 10

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 4
eloc 11
nc 10
nop 0
dl 0
loc 19
ccs 0
cts 9
cp 0
crap 20
rs 9.2
c 1
b 0
f 1
1
<?php
2
3
/*
4
 * This file is part of SwiftMailer.
5
 * (c) 2004-2009 Chris Corbyn
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
/**
12
 * Sends Messages over SMTP.
13
 *
14
 * @author Chris Corbyn
15
 */
16
abstract class Swift_Transport_AbstractSmtpTransport implements Swift_Transport
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
17
{
18
    /**
19
     * Input-Output buffer for sending/receiving SMTP commands and responses
20
     *
21
     * @var Swift_Transport_IoBuffer|Swift_Transport_StreamBuffer
22
     */
23
    protected $_buffer;
24
25
    /**
26
     * Connection status
27
     *
28
     * @var bool
29
     */
30
    protected $_started = false;
31
32
    /**
33
     * The domain name to use in HELO command
34
     *
35
     * @var string
36
     */
37
    protected $_domain = '[127.0.0.1]';
38
39
    /**
40
     * The event dispatching layer
41
     *
42
     * @var Swift_Events_EventDispatcher
43
     */
44
    protected $_eventDispatcher;
45
46
    /**
47
     * Source Ip
48
     *
49
     * @var string
50
     */
51
    protected $_sourceIp;
52
53
    /**
54
     * Return an array of params for the Buffer
55
     */
56
    abstract protected function _getBufferParams();
57
58
    /**
59
     * Creates a new EsmtpTransport using the given I/O buffer.
60
     *
61
     * @param Swift_Transport_IoBuffer     $buf
62
     * @param Swift_Events_EventDispatcher $dispatcher
63
     */
64 173
    public function __construct(Swift_Transport_IoBuffer $buf, Swift_Events_EventDispatcher $dispatcher)
65
    {
66 173
        $this->_eventDispatcher = $dispatcher;
67 173
        $this->_buffer = $buf;
68 173
        $this->_lookupHostname();
69 173
    }
70
71
    /**
72
     * Set the name of the local domain which Swift will identify itself as.
73
     *
74
     * This should be a fully-qualified domain name and should be truly the domain
75
     * you're using.
76
     *
77
     * If your server doesn't have a domain name, use the IP in square
78
     * brackets (i.e. [127.0.0.1]).
79
     *
80
     * @param string $domain
81
     *
82
     * @return Swift_Transport_AbstractSmtpTransport
83
     */
84 5
    public function setLocalDomain($domain)
85
    {
86 5
        $this->_domain = $domain;
87
88 5
        return $this;
89
    }
90
91
    /**
92
     * Get the name of the domain Swift will identify as.
93
     *
94
     * @return string
95
     */
96
    public function getLocalDomain()
97
    {
98
        return $this->_domain;
99
    }
100
101
    /**
102
     * Sets the source IP.
103
     *
104
     * @param string $source
105
     */
106
    public function setSourceIp($source)
107
    {
108
        $this->_sourceIp = $source;
109
    }
110
111
    /**
112
     * Returns the IP used to connect to the destination.
113
     *
114
     * @return string
115
     */
116
    public function getSourceIp()
117
    {
118
        return $this->_sourceIp;
119
    }
120
121
    /**
122
     * Start the SMTP connection.
123
     */
124 140
    public function start()
125
    {
126 140
        if (!$this->_started) {
127
128 140
            $evt = $this->_eventDispatcher->createTransportChangeEvent($this);
129 140
            if ($evt) {
130
131 22
                $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted');
132 22
                if ($evt->bubbleCancelled()) {
133 3
                    return;
134
                }
135
            }
136
137
            try {
138 137
                $this->_buffer->initialize($this->_getBufferParams());
139
            } catch (Swift_TransportException $e) {
140
                $this->_throwException($e);
141
            }
142 137
            $this->_readGreeting();
143 131
            $this->_doHeloCommand();
144
145 128
            if ($evt) {
146 19
                $this->_eventDispatcher->dispatchEvent($evt, 'transportStarted');
147
            }
148
149 128
            $this->_started = true;
150
        }
151 128
    }
152
153
    /**
154
     * Test if an SMTP connection has been established.
155
     *
156
     * @return bool
157
     */
158 22
    public function isStarted()
159
    {
160 22
        return $this->_started;
161
    }
162
163
    /**
164
     * Send the given Message.
165
     *
166
     * Recipient/sender data will be retrieved from the Message API.
167
     * The return value is the number of recipients who were accepted for delivery.
168
     *
169
     * @param Swift_Mime_Message $message
170
     * @param string[]|null      $failedRecipients An array of failures by-reference
171
     *
172
     * @return int
173
     *
174
     * @throws Exception
175
     * @throws Swift_TransportException
176
     */
177 84
    public function send(Swift_Mime_Message $message, &$failedRecipients = null)
178
    {
179 84
        $sent = 0;
180 84
        $failedRecipients = (array)$failedRecipients;
181
182 84
        $evt = $this->_eventDispatcher->createSendEvent($this, $message);
183 84
        if ($evt) {
184
185 25
            $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
186 25
            if ($evt->bubbleCancelled()) {
187 3
                return 0;
188
            }
189
        }
190
191 81
        if (!$reversePath = $this->_getReversePath($message)) {
192
            $this->_throwException(
193
                new Swift_TransportException(
194
                    'Cannot send message without a sender address'
195
                )
196
            );
197
        }
198
199 81
        $to = (array)$message->getTo();
200 81
        $cc = (array)$message->getCc();
201 81
        $tos = array_merge($to, $cc);
202 81
        $bcc = (array)$message->getBcc();
203
204 81
        $message->setBcc(array());
205
206
        try {
207 81
            $sent += $this->_sendTo($message, $reversePath, $tos, $failedRecipients);
208 69
            $sent += $this->_sendBcc($message, $reversePath, $bcc, $failedRecipients);
209 12
        } catch (Exception $e) {
210 12
            $message->setBcc($bcc);
211 12
            throw $e;
212
        }
213
214 69
        $message->setBcc($bcc);
215
216 69
        if ($evt) {
217
218 22
            if ($sent === count($to) + count($cc) + count($bcc)) {
219 13
                $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
220 9
            } elseif ($sent > 0) {
221 3
                $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE);
222
            } else {
223 6
                $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
224
            }
225
226 22
            $evt->setFailedRecipients($failedRecipients);
227 22
            $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
228
        }
229
230 69
        $message->generateId(); // Make sure a new Message ID is used
231
232 69
        return $sent;
233
    }
234
235
    /**
236
     * Stop the SMTP connection.
237
     */
238 39
    public function stop()
239
    {
240 39
        if ($this->_started) {
241
242 16
            $evt = $this->_eventDispatcher->createTransportChangeEvent($this);
243 16
            if ($evt) {
244
245 13
                $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped');
246 13
                if ($evt->bubbleCancelled()) {
247 3
                    return;
248
                }
249
            }
250
251
            try {
252 13
                $this->executeCommand("QUIT\r\n", array(221));
253 6
            } catch (Swift_TransportException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
254
            }
255
256
            try {
257 13
                $this->_buffer->terminate();
258
259 13
                if ($evt) {
260 13
                    $this->_eventDispatcher->dispatchEvent($evt, 'transportStopped');
261
                }
262
            } catch (Swift_TransportException $e) {
263
                $this->_throwException($e);
264
            }
265
        }
266 36
        $this->_started = false;
267 36
    }
268
269
    /**
270
     * Check if this Transport mechanism is alive.
271
     *
272
     * If a Transport mechanism session is no longer functional, the method
273
     * returns FALSE. It is the responsibility of the developer to handle this
274
     * case and restart the Transport mechanism manually.
275
     *
276
     * @example
277
     *
278
     *   if (!$transport->ping()) {
279
     *      $transport->stop();
280
     *      $transport->start();
281
     *   }
282
     *
283
     * The Transport mechanism will be started, if it is not already.
284
     *
285
     * It is undefined if the Transport mechanism attempts to restart as long as
286
     * the return value reflects whether the mechanism is now functional.
287
     *
288
     * @return bool TRUE if the transport is alive
289
     */
290
    public function ping()
291
    {
292
        try {
293
            if (!$this->isStarted()) {
294
                $this->start();
295
            }
296
297
            $this->executeCommand("NOOP\r\n", array(250));
298
        } catch (Swift_TransportException $e) {
299
            try {
300
                $this->stop();
301
            } catch (Swift_TransportException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
302
            }
303
304
            return false;
305
        }
306
307
        return true;
308
    }
309
310
    /**
311
     * Register a plugin.
312
     *
313
     * @param Swift_Events_EventListener $plugin
314
     */
315 3
    public function registerPlugin(Swift_Events_EventListener $plugin)
316
    {
317 3
        $this->_eventDispatcher->bindEventListener($plugin);
318 3
    }
319
320
    /**
321
     * Reset the current mail transaction.
322
     */
323 12
    public function reset()
324
    {
325 12
        $this->executeCommand("RSET\r\n", array(250));
326 12
    }
327
328
    /**
329
     * Get the IoBuffer where read/writes are occurring.
330
     *
331
     * @return Swift_Transport_IoBuffer
332
     */
333 33
    public function getBuffer()
334
    {
335 33
        return $this->_buffer;
336
    }
337
338
    /**
339
     * Run a command against the buffer, expecting the given response codes.
340
     *
341
     * If no response codes are given, the response will not be validated.
342
     * If codes are given, an exception will be thrown on an invalid response.
343
     *
344
     * @param string   $command
345
     * @param int[]    $codes
346
     * @param string[] $failures An array of failures by-reference
347
     *
348
     * @return string
349
     */
350 137
    public function executeCommand($command, $codes = array(), &$failures = null)
351
    {
352 137
        $failures = (array)$failures;
353 137
        $seq = $this->_buffer->write($command);
354 137
        $response = $this->_getFullResponse($seq);
355
356 137
        $evt = $this->_eventDispatcher->createCommandEvent($this, $command, $codes);
357 137
        if ($evt) {
358 7
            $this->_eventDispatcher->dispatchEvent($evt, 'commandSent');
359
        }
360
361 137
        $this->_assertResponseCode($response, $codes);
362
363 131
        return $response;
364
    }
365
366
    /** Read the opening SMTP greeting */
367 137
    protected function _readGreeting()
368
    {
369 137
        $this->_assertResponseCode($this->_getFullResponse(0), array(220));
370 131
    }
371
372
    /** Send the HELO welcome */
373 45
    protected function _doHeloCommand()
374
    {
375 45
        $this->executeCommand(
376 45
            sprintf("HELO %s\r\n", $this->_domain), array(250)
377
        );
378 42
    }
379
380
    /**
381
     * Send the MAIL FROM command
382
     *
383
     * @param string $address
384
     */
385 25
    protected function _doMailFromCommand($address)
386
    {
387 25
        $this->executeCommand(
388 25
            sprintf("MAIL FROM:<%s>\r\n", $address), array(250)
389
        );
390 24
    }
391
392
    /**
393
     * Send the RCPT TO command
394
     *
395
     * @param string $address
396
     */
397 24
    protected function _doRcptToCommand($address)
398
    {
399 24
        $this->executeCommand(
400 24
            sprintf("RCPT TO:<%s>\r\n", $address), array(250, 251, 252)
401
        );
402 21
    }
403
404
    /** Send the DATA command */
405 69
    protected function _doDataCommand()
406
    {
407 69
        $this->executeCommand("DATA\r\n", array(354));
408 63
    }
409
410
    /**
411
     * Stream the contents of the message over the buffer
412
     *
413
     * @param Swift_Mime_Message $message
414
     *
415
     * @throws Swift_TransportException
416
     */
417 63
    protected function _streamMessage(Swift_Mime_Message $message)
418
    {
419 63
        $this->_buffer->setWriteTranslations(array("\r\n." => "\r\n.."));
420
421
        try {
422 63
            $message->toByteStream($this->_buffer);
423 63
            $this->_buffer->flushBuffers();
424
        } catch (Swift_TransportException $e) {
425
            $this->_throwException($e);
426
        }
427
428 63
        $this->_buffer->setWriteTranslations(array());
429 63
        $this->executeCommand("\r\n.\r\n", array(250));
430 60
    }
431
432
    /**
433
     * Determine the best-use reverse path for this message
434
     *
435
     * @param Swift_Mime_Message $message
436
     *
437
     * @return mixed|null|string
438
     */
439 85 View Code Duplication
    protected function _getReversePath(Swift_Mime_Message $message)
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...
440
    {
441 85
        $return = $message->getReturnPath();
442 85
        $sender = $message->getSender();
443 85
        $from = $message->getFrom();
444 85
        $path = null;
445
446 85
        if (!empty($return)) {
447 3
            $path = $return;
448 82
        } elseif (!empty($sender)) {
449
            // don't use array_keys
450 3
            reset($sender); // reset Pointer to first pos
451 3
            $path = key($sender); // get key
452 79
        } elseif (!empty($from)) {
453 75
            reset($from); // reset Pointer to first pos
454 75
            $path = key($from); // get key
455
        }
456
457 85
        return $path;
458
    }
459
460
    /**
461
     * Throw a TransportException, first sending it to any listeners.
462
     *
463
     * @param Swift_TransportException $e
464
     *
465
     * @throws Swift_TransportException
466
     */
467 55 View Code Duplication
    protected function _throwException(Swift_TransportException $e)
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...
468
    {
469 55
        $evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e);
470 55
        if ($evt) {
471
472 6
            $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown');
473 6
            if (!$evt->bubbleCancelled()) {
474 6
                throw $e;
475
            }
476
477
        } else {
478 49
            throw $e;
479
        }
480 3
    }
481
482
    /**
483
     * Throws an Exception if a response code is incorrect
484
     *
485
     * @param string    $response
486
     * @param integer[] $wanted
487
     *
488
     * @throws Swift_TransportException
489
     */
490 143
    protected function _assertResponseCode($response, $wanted)
491
    {
492
        /** @noinspection PrintfScanfArgumentsInspection */
493 143
        list($code) = sscanf($response, '%3d');
494
495
        /** @noinspection TypeUnsafeArraySearchInspection */
496 143
        $valid = (empty($wanted) || in_array($code, $wanted));
497
498 143
        $evt = $this->_eventDispatcher->createResponseEvent($this, $response, $valid);
499 143
        if ($evt) {
500 7
            $this->_eventDispatcher->dispatchEvent($evt, 'responseReceived');
501
        }
502
503 143
        if (!$valid) {
504 55
            $this->_throwException(
505 55
                new Swift_TransportException(
506 55
                    'Expected response code ' . implode('/', $wanted) . ' but got code ' .
507 55
                    '"' . $code . '", with message "' . $response . '"',
508
                    $code
509
                )
510
            );
511
        }
512 134
    }
513
514
    /**
515
     * Get an entire multi-line response using its sequence number
516
     *
517
     * @param int $seq
518
     *
519
     * @return string
520
     * @throws Swift_TransportException
521
     */
522 143
    protected function _getFullResponse($seq)
523
    {
524 143
        $response = '';
525
        try {
526
            do {
527 143
                $line = $this->_buffer->readLine($seq);
528 143
                $response .= $line;
529 143
            } while (null !== $line && false !== $line && ' ' !== $line[3]);
530
        } catch (Swift_TransportException $e) {
531
            $this->_throwException($e);
532
        } catch (Swift_IoException $e) {
533
            $this->_throwException(
534
                new Swift_TransportException(
535
                    $e->getMessage()
536
                )
537
            );
538
        }
539
540 143
        return $response;
541
    }
542
543
    /**
544
     * Send an email to the given recipients from the given reverse path
545
     *
546
     * @param Swift_Mime_Message $message
547
     * @param string             $reversePath
548
     * @param array              $recipients
549
     * @param array              $failedRecipients
550
     *
551
     * @return int
552
     */
553 81
    private function _doMailTransaction($message, $reversePath, array $recipients, array &$failedRecipients)
554
    {
555 81
        $sent = 0;
556 81
        $this->_doMailFromCommand($reversePath);
557
558 78
        foreach ($recipients as $forwardPath) {
559
            try {
560 78
                $this->_doRcptToCommand($forwardPath);
561 69
                ++$sent;
562 18
            } catch (Swift_TransportException $e) {
563 78
                $failedRecipients[] = $forwardPath;
564
565
                // throw $e;
566
                // <-- TODO: check this
567
                // <-- https://github.com/michaelhogg/swiftmailer/commit/b824cba068d10c46291018947e463cb201a3e572
568
            }
569
        }
570
571 78
        if ($sent != 0) {
572 69
            $this->_doDataCommand();
573 63
            $this->_streamMessage($message);
574
        } else {
575 12
            $this->reset();
576
        }
577
578 69
        return $sent;
579
    }
580
581
    /**
582
     * Send a message to the given To: recipients
583
     *
584
     * @param Swift_Mime_Message $message
585
     * @param                    $reversePath
586
     * @param array              $to
587
     * @param array              $failedRecipients
588
     *
589
     * @return int
590
     */
591 81
    private function _sendTo(Swift_Mime_Message $message, $reversePath, array $to, array &$failedRecipients)
592
    {
593 81
        if (empty($to)) {
594
            return 0;
595
        }
596
597 81
        return $this->_doMailTransaction($message, $reversePath, array_keys($to), $failedRecipients);
598
    }
599
600
    /**
601
     * Send a message to all Bcc: recipients
602
     *
603
     * @param Swift_Mime_Message $message
604
     * @param                    $reversePath
605
     * @param array              $bcc
606
     * @param array              $failedRecipients
607
     *
608
     * @return int
609
     */
610 69
    private function _sendBcc(Swift_Mime_Message $message, $reversePath, array $bcc, array &$failedRecipients)
611
    {
612 69
        $sent = 0;
613 69
        foreach ($bcc as $forwardPath => $name) {
614 9
            $message->setBcc(array($forwardPath => $name));
615 9
            $sent += $this->_doMailTransaction(
616
                $message,
617
                $reversePath,
618 9
                array($forwardPath),
619
                $failedRecipients
620
            );
621
        }
622
623 69
        return $sent;
624
    }
625
626
    /**
627
     * Try to determine the hostname of the server this is run on
628
     *
629
     * @return bool
630
     */
631 173
    private function _lookupHostname()
0 ignored issues
show
Coding Style introduced by
_lookupHostname uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
632
    {
633
        if (
634 173
            !empty($_SERVER['SERVER_NAME'])
635
            &&
636 173
            $this->_isFqdn($_SERVER['SERVER_NAME'])
637
        ) {
638
            $this->_domain = $_SERVER['SERVER_NAME'];
639
640
            return true;
641
        }
642
643 173
        if (!empty($_SERVER['SERVER_ADDR'])) {
644
645
            // Set the address literal tag (See RFC 5321, section: 4.1.3)
646
            if (false === strpos($_SERVER['SERVER_ADDR'], ':')) {
647
                $prefix = ''; // IPv4 addresses are not tagged.
648
            } else {
649
                $prefix = 'IPv6:'; // Adding prefix in case of IPv6.
650
            }
651
652
            $this->_domain = sprintf('[%s%s]', $prefix, $_SERVER['SERVER_ADDR']);
653
654
            return true;
655
        }
656
657 173
        return false;
658
    }
659
660
    /**
661
     * Determine is the $hostname is a fully-qualified name
662
     *
663
     * @param $hostname
664
     *
665
     * @return bool
666
     */
667
    private function _isFqdn($hostname)
668
    {
669
        // We could do a really thorough check, but there's really no point.
670
        $dotPos = strpos($hostname, '.');
671
672
        if (false === $dotPos) {
673
            return false;
674
        }
675
676
        return ($dotPos > 0) && ($dotPos !== strlen($hostname) - 1);
677
    }
678
679
    /**
680
     * Destructor.
681
     */
682 27
    public function __destruct()
683
    {
684 27
        $this->stop();
685 27
    }
686
}
687