Completed
Push — 5.x ( 5d59f7...0928cf )
by Lars
04:34
created

_getFullResponse()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 10.5

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 6
eloc 14
c 1
b 1
f 0
nc 5
nop 1
dl 0
loc 20
ccs 6
cts 12
cp 0.5
crap 10.5
rs 8.8571
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 54
            $sent += $this->_sendBcc($message, $reversePath, $bcc, $failedRecipients);
209 30
        } catch (Exception $e) {
210 30
            $message->setBcc($bcc);
211 30
            throw $e;
212
        }
213
214 51
        $message->setBcc($bcc);
215
216 51
        if ($evt) {
217
218 13
            if ($sent === count($to) + count($cc) + count($bcc)) {
219 13
                $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
220
            } elseif ($sent > 0) {
221
                $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE);
222
            } else {
223
                $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
224
            }
225
226 13
            $evt->setFailedRecipients($failedRecipients);
227 13
            $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
228
        }
229
230 51
        $message->generateId(); // Make sure a new Message ID is used
231
232 51
        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
     * Register a plugin.
271
     *
272
     * @param Swift_Events_EventListener $plugin
273
     */
274 3
    public function registerPlugin(Swift_Events_EventListener $plugin)
275
    {
276 3
        $this->_eventDispatcher->bindEventListener($plugin);
277 3
    }
278
279
    /**
280
     * Reset the current mail transaction.
281
     */
282
    public function reset()
283
    {
284
        $this->executeCommand("RSET\r\n", array(250));
285
    }
286
287
    /**
288
     * Get the IoBuffer where read/writes are occurring.
289
     *
290
     * @return Swift_Transport_IoBuffer
291
     */
292 33
    public function getBuffer()
293
    {
294 33
        return $this->_buffer;
295
    }
296
297
    /**
298
     * Run a command against the buffer, expecting the given response codes.
299
     *
300
     * If no response codes are given, the response will not be validated.
301
     * If codes are given, an exception will be thrown on an invalid response.
302
     *
303
     * @param string   $command
304
     * @param int[]    $codes
305
     * @param string[] $failures An array of failures by-reference
306
     *
307
     * @return string
308
     */
309 137
    public function executeCommand($command, $codes = array(), &$failures = null)
310
    {
311 137
        $failures = (array)$failures;
312 137
        $seq = $this->_buffer->write($command);
313 137
        $response = $this->_getFullResponse($seq);
314
315 137
        $evt = $this->_eventDispatcher->createCommandEvent($this, $command, $codes);
316 137
        if ($evt) {
317 7
            $this->_eventDispatcher->dispatchEvent($evt, 'commandSent');
318
        }
319
320 137
        $this->_assertResponseCode($response, $codes);
321
322 131
        return $response;
323
    }
324
325
    /** Read the opening SMTP greeting */
326 137
    protected function _readGreeting()
327
    {
328 137
        $this->_assertResponseCode($this->_getFullResponse(0), array(220));
329 131
    }
330
331
    /** Send the HELO welcome */
332 45
    protected function _doHeloCommand()
333
    {
334 45
        $this->executeCommand(
335 45
            sprintf("HELO %s\r\n", $this->_domain), array(250)
336
        );
337 42
    }
338
339
    /**
340
     * Send the MAIL FROM command
341
     *
342
     * @param string $address
343
     */
344 25
    protected function _doMailFromCommand($address)
345
    {
346 25
        $this->executeCommand(
347 25
            sprintf("MAIL FROM:<%s>\r\n", $address), array(250)
348
        );
349 24
    }
350
351
    /**
352
     * Send the RCPT TO command
353
     *
354
     * @param string $address
355
     */
356 24
    protected function _doRcptToCommand($address)
357
    {
358 24
        $this->executeCommand(
359 24
            sprintf("RCPT TO:<%s>\r\n", $address), array(250, 251, 252)
360
        );
361 20
    }
362
363
    /** Send the DATA command */
364 63
    protected function _doDataCommand()
365
    {
366 63
        $this->executeCommand("DATA\r\n", array(354));
367 57
    }
368
369
    /**
370
     * Stream the contents of the message over the buffer
371
     *
372
     * @param Swift_Mime_Message $message
373
     *
374
     * @throws Swift_TransportException
375
     */
376 57
    protected function _streamMessage(Swift_Mime_Message $message)
377
    {
378 57
        $this->_buffer->setWriteTranslations(array("\r\n." => "\r\n.."));
379
380
        try {
381 57
            $message->toByteStream($this->_buffer);
382 57
            $this->_buffer->flushBuffers();
383
        } catch (Swift_TransportException $e) {
384
            $this->_throwException($e);
385
        }
386
387 57
        $this->_buffer->setWriteTranslations(array());
388 57
        $this->executeCommand("\r\n.\r\n", array(250));
389 54
    }
390
391
    /**
392
     * Determine the best-use reverse path for this message
393
     *
394
     * @param Swift_Mime_Message $message
395
     *
396
     * @return mixed|null|string
397
     */
398 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...
399
    {
400 85
        $return = $message->getReturnPath();
401 85
        $sender = $message->getSender();
402 85
        $from = $message->getFrom();
403 85
        $path = null;
404
405 85
        if (!empty($return)) {
406 3
            $path = $return;
407 82
        } elseif (!empty($sender)) {
408
            // don't use array_keys
409 3
            reset($sender); // reset Pointer to first pos
410 3
            $path = key($sender); // get key
411 79
        } elseif (!empty($from)) {
412 75
            reset($from); // reset Pointer to first pos
413 75
            $path = key($from); // get key
414
        }
415
416 85
        return $path;
417
    }
418
419
    /**
420
     * Throw a TransportException, first sending it to any listeners.
421
     *
422
     * @param Swift_TransportException $e
423
     *
424
     * @throws Swift_TransportException
425
     */
426 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...
427
    {
428 55
        $evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e);
429 55
        if ($evt) {
430
431 6
            $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown');
432 6
            if (!$evt->bubbleCancelled()) {
433 6
                throw $e;
434
            }
435
436
        } else {
437 49
            throw $e;
438
        }
439 3
    }
440
441
    /**
442
     * Throws an Exception if a response code is incorrect
443
     *
444
     * @param string    $response
445
     * @param integer[] $wanted
446
     *
447
     * @throws Swift_TransportException
448
     */
449 143
    protected function _assertResponseCode($response, $wanted)
450
    {
451
        /** @noinspection PrintfScanfArgumentsInspection */
452 143
        list($code) = sscanf($response, '%3d');
453
454
        /** @noinspection TypeUnsafeArraySearchInspection */
455 143
        $valid = (empty($wanted) || in_array($code, $wanted));
456
457 143
        $evt = $this->_eventDispatcher->createResponseEvent($this, $response, $valid);
458 143
        if ($evt) {
459 7
            $this->_eventDispatcher->dispatchEvent($evt, 'responseReceived');
460
        }
461
462 143
        if (!$valid) {
463 55
            $this->_throwException(
464 55
                new Swift_TransportException(
465 55
                    'Expected response code ' . implode('/', $wanted) . ' but got code ' .
466 55
                    '"' . $code . '", with message "' . $response . '"',
467
                    $code
468
                )
469
            );
470
        }
471 134
    }
472
473
    /**
474
     * Get an entire multi-line response using its sequence number
475
     *
476
     * @param int $seq
477
     *
478
     * @return string
479
     * @throws Swift_TransportException
480
     */
481 143
    protected function _getFullResponse($seq)
482
    {
483 143
        $response = '';
484
        try {
485
            do {
486 143
                $line = $this->_buffer->readLine($seq);
487 143
                $response .= $line;
488 143
            } while (null !== $line && false !== $line && ' ' !== $line[3]);
489
        } catch (Swift_TransportException $e) {
490
            $this->_throwException($e);
491
        } catch (Swift_IoException $e) {
492
            $this->_throwException(
493
                new Swift_TransportException(
494
                    $e->getMessage()
495
                )
496
            );
497
        }
498
499 143
        return $response;
500
    }
501
502
    /**
503
     * Send an email to the given recipients from the given reverse path
504
     *
505
     * @param Swift_Mime_Message $message
506
     * @param string             $reversePath
507
     * @param array              $recipients
508
     * @param array              $failedRecipients
509
     *
510
     * @return int
511
     *
512
     * @throws Swift_TransportException
513
     */
514 81
    private function _doMailTransaction($message, $reversePath, array $recipients, array &$failedRecipients)
515
    {
516 81
        $sent = 0;
517 81
        $this->_doMailFromCommand($reversePath);
518
519 78
        foreach ($recipients as $forwardPath) {
520
            try {
521 78
                $this->_doRcptToCommand($forwardPath);
522 66
                ++$sent;
523 18
            } catch (Swift_TransportException $e) {
524 18
                $failedRecipients[] = $forwardPath;
525
526 78
                $this->_throwException($e);
527
            }
528
        }
529
530 63
        if ($sent !== 0) {
531 63
            $this->_doDataCommand();
532 57
            $this->_streamMessage($message);
533
        } else {
534
            $this->reset();
535
        }
536
537 54
        return $sent;
538
    }
539
540
    /**
541
     * Send a message to the given To: recipients
542
     *
543
     * @param Swift_Mime_Message $message
544
     * @param                    $reversePath
545
     * @param array              $to
546
     * @param array              $failedRecipients
547
     *
548
     * @return int
549
     *
550
     * @throws Swift_TransportException
551
     */
552 81
    private function _sendTo(Swift_Mime_Message $message, $reversePath, array $to, array &$failedRecipients)
553
    {
554 81
        if (empty($to)) {
555
            return 0;
556
        }
557
558 81
        return $this->_doMailTransaction($message, $reversePath, array_keys($to), $failedRecipients);
559
    }
560
561
    /**
562
     * Send a message to all Bcc: recipients
563
     *
564
     * @param Swift_Mime_Message $message
565
     * @param                    $reversePath
566
     * @param array              $bcc
567
     * @param array              $failedRecipients
568
     *
569
     * @return int
570
     *
571
     * @throws Swift_TransportException
572
     */
573 54
    private function _sendBcc(Swift_Mime_Message $message, $reversePath, array $bcc, array &$failedRecipients)
574
    {
575 54
        $sent = 0;
576 54
        foreach ($bcc as $forwardPath => $name) {
577 9
            $message->setBcc(array($forwardPath => $name));
578 9
            $sent += $this->_doMailTransaction(
579
                $message,
580
                $reversePath,
581 9
                array($forwardPath),
582
                $failedRecipients
583
            );
584
        }
585
586 51
        return $sent;
587
    }
588
589
    /**
590
     * Try to determine the hostname of the server this is run on
591
     *
592
     * @return bool
593
     */
594 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...
595
    {
596
        if (
597 173
            !empty($_SERVER['SERVER_NAME'])
598
            &&
599 173
            $this->_isFqdn($_SERVER['SERVER_NAME'])
600
        ) {
601
            $this->_domain = $_SERVER['SERVER_NAME'];
602
603
            return true;
604
        }
605
606 173
        if (!empty($_SERVER['SERVER_ADDR'])) {
607
608
            // Set the address literal tag (See RFC 5321, section: 4.1.3)
609
            if (false === strpos($_SERVER['SERVER_ADDR'], ':')) {
610
                $prefix = ''; // IPv4 addresses are not tagged.
611
            } else {
612
                $prefix = 'IPv6:'; // Adding prefix in case of IPv6.
613
            }
614
615
            $this->_domain = sprintf('[%s%s]', $prefix, $_SERVER['SERVER_ADDR']);
616
617
            return true;
618
        }
619
620 173
        return false;
621
    }
622
623
    /**
624
     * Determine is the $hostname is a fully-qualified name
625
     *
626
     * @param $hostname
627
     *
628
     * @return bool
629
     */
630
    private function _isFqdn($hostname)
631
    {
632
        // We could do a really thorough check, but there's really no point.
633
        $dotPos = strpos($hostname, '.');
634
635
        if (false === $dotPos) {
636
            return false;
637
        }
638
639
        return ($dotPos > 0) && ($dotPos !== strlen($hostname) - 1);
640
    }
641
642
    /**
643
     * Destructor.
644
     */
645 27
    public function __destruct()
646
    {
647 27
        $this->stop();
648 27
    }
649
}
650