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