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 using the mail() function. 13 * 14 * It is advised that users do not use this transport if at all possible 15 * since a number of plugin features cannot be used in conjunction with this 16 * transport due to the internal interface in PHP itself. 17 * 18 * The level of error reporting with this transport is incredibly weak, again 19 * due to limitations of PHP's internal mail() function. You'll get an 20 * all-or-nothing result from sending. 21 * 22 * @author Chris Corbyn 23 * 24 * @deprecated since 5.4.5 (to be removed in 6.0) 25 */ 26class Swift_Transport_MailTransport implements Swift_Transport 27{ 28 /** Additional parameters to pass to mail() */ 29 private $_extraParams = '-f%s'; 30 31 /** The event dispatcher from the plugin API */ 32 private $_eventDispatcher; 33 34 /** An invoker that calls the mail() function */ 35 private $_invoker; 36 37 /** 38 * Create a new MailTransport with the $log. 39 * 40 * @param Swift_Transport_MailInvoker $invoker 41 * @param Swift_Events_EventDispatcher $eventDispatcher 42 */ 43 public function __construct(Swift_Transport_MailInvoker $invoker, Swift_Events_EventDispatcher $eventDispatcher) 44 { 45 @trigger_error(sprintf('The %s class is deprecated since version 5.4.5 and will be removed in 6.0. Use the Sendmail or SMTP transport instead.', __CLASS__), E_USER_DEPRECATED); 46 47 $this->_invoker = $invoker; 48 $this->_eventDispatcher = $eventDispatcher; 49 } 50 51 /** 52 * Not used. 53 */ 54 public function isStarted() 55 { 56 return false; 57 } 58 59 /** 60 * Not used. 61 */ 62 public function start() 63 { 64 } 65 66 /** 67 * Not used. 68 */ 69 public function stop() 70 { 71 } 72 73 /** 74 * Set the additional parameters used on the mail() function. 75 * 76 * This string is formatted for sprintf() where %s is the sender address. 77 * 78 * @param string $params 79 * 80 * @return $this 81 */ 82 public function setExtraParams($params) 83 { 84 $this->_extraParams = $params; 85 86 return $this; 87 } 88 89 /** 90 * Get the additional parameters used on the mail() function. 91 * 92 * This string is formatted for sprintf() where %s is the sender address. 93 * 94 * @return string 95 */ 96 public function getExtraParams() 97 { 98 return $this->_extraParams; 99 } 100 101 /** 102 * Send the given Message. 103 * 104 * Recipient/sender data will be retrieved from the Message API. 105 * The return value is the number of recipients who were accepted for delivery. 106 * 107 * @param Swift_Mime_Message $message 108 * @param string[] $failedRecipients An array of failures by-reference 109 * 110 * @return int 111 */ 112 public function send(Swift_Mime_Message $message, &$failedRecipients = null) 113 { 114 $failedRecipients = (array) $failedRecipients; 115 116 if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) { 117 $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); 118 if ($evt->bubbleCancelled()) { 119 return 0; 120 } 121 } 122 123 $count = ( 124 count((array) $message->getTo()) 125 + count((array) $message->getCc()) 126 + count((array) $message->getBcc()) 127 ); 128 129 $toHeader = $message->getHeaders()->get('To'); 130 $subjectHeader = $message->getHeaders()->get('Subject'); 131 132 if (0 === $count) { 133 $this->_throwException(new Swift_TransportException('Cannot send message without a recipient')); 134 } 135 $to = $toHeader ? $toHeader->getFieldBody() : ''; 136 $subject = $subjectHeader ? $subjectHeader->getFieldBody() : ''; 137 138 $reversePath = $this->_getReversePath($message); 139 140 // Remove headers that would otherwise be duplicated 141 $message->getHeaders()->remove('To'); 142 $message->getHeaders()->remove('Subject'); 143 144 $messageStr = $message->toString(); 145 146 if ($toHeader) { 147 $message->getHeaders()->set($toHeader); 148 } 149 $message->getHeaders()->set($subjectHeader); 150 151 // Separate headers from body 152 if (false !== $endHeaders = strpos($messageStr, "\r\n\r\n")) { 153 $headers = substr($messageStr, 0, $endHeaders)."\r\n"; //Keep last EOL 154 $body = substr($messageStr, $endHeaders + 4); 155 } else { 156 $headers = $messageStr."\r\n"; 157 $body = ''; 158 } 159 160 unset($messageStr); 161 162 if ("\r\n" != PHP_EOL) { 163 // Non-windows (not using SMTP) 164 $headers = str_replace("\r\n", PHP_EOL, $headers); 165 $subject = str_replace("\r\n", PHP_EOL, $subject); 166 $body = str_replace("\r\n", PHP_EOL, $body); 167 $to = str_replace("\r\n", PHP_EOL, $to); 168 } else { 169 // Windows, using SMTP 170 $headers = str_replace("\r\n.", "\r\n..", $headers); 171 $subject = str_replace("\r\n.", "\r\n..", $subject); 172 $body = str_replace("\r\n.", "\r\n..", $body); 173 $to = str_replace("\r\n.", "\r\n..", $to); 174 } 175 176 if ($this->_invoker->mail($to, $subject, $body, $headers, $this->_formatExtraParams($this->_extraParams, $reversePath))) { 177 if ($evt) { 178 $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); 179 $evt->setFailedRecipients($failedRecipients); 180 $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); 181 } 182 } else { 183 $failedRecipients = array_merge( 184 $failedRecipients, 185 array_keys((array) $message->getTo()), 186 array_keys((array) $message->getCc()), 187 array_keys((array) $message->getBcc()) 188 ); 189 190 if ($evt) { 191 $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED); 192 $evt->setFailedRecipients($failedRecipients); 193 $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); 194 } 195 196 $message->generateId(); 197 198 $count = 0; 199 } 200 201 return $count; 202 } 203 204 /** 205 * Register a plugin. 206 * 207 * @param Swift_Events_EventListener $plugin 208 */ 209 public function registerPlugin(Swift_Events_EventListener $plugin) 210 { 211 $this->_eventDispatcher->bindEventListener($plugin); 212 } 213 214 /** Throw a TransportException, first sending it to any listeners */ 215 protected function _throwException(Swift_TransportException $e) 216 { 217 if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e)) { 218 $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown'); 219 if (!$evt->bubbleCancelled()) { 220 throw $e; 221 } 222 } else { 223 throw $e; 224 } 225 } 226 227 /** Determine the best-use reverse path for this message */ 228 private function _getReversePath(Swift_Mime_Message $message) 229 { 230 $return = $message->getReturnPath(); 231 $sender = $message->getSender(); 232 $from = $message->getFrom(); 233 $path = null; 234 if (!empty($return)) { 235 $path = $return; 236 } elseif (!empty($sender)) { 237 $keys = array_keys($sender); 238 $path = array_shift($keys); 239 } elseif (!empty($from)) { 240 $keys = array_keys($from); 241 $path = array_shift($keys); 242 } 243 244 return $path; 245 } 246 247 /** 248 * Fix CVE-2016-10074 by disallowing potentially unsafe shell characters. 249 * 250 * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. 251 * 252 * @param string $string The string to be validated 253 * 254 * @return bool 255 */ 256 private function _isShellSafe($string) 257 { 258 // Future-proof 259 if (escapeshellcmd($string) !== $string || !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))) { 260 return false; 261 } 262 263 $length = strlen($string); 264 for ($i = 0; $i < $length; ++$i) { 265 $c = $string[$i]; 266 // All other characters have a special meaning in at least one common shell, including = and +. 267 // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. 268 // Note that this does permit non-Latin alphanumeric characters based on the current locale. 269 if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { 270 return false; 271 } 272 } 273 274 return true; 275 } 276 277 /** 278 * Return php mail extra params to use for invoker->mail. 279 * 280 * @param $extraParams 281 * @param $reversePath 282 * 283 * @return string|null 284 */ 285 private function _formatExtraParams($extraParams, $reversePath) 286 { 287 if (false !== strpos($extraParams, '-f%s')) { 288 if (empty($reversePath) || false === $this->_isShellSafe($reversePath)) { 289 $extraParams = str_replace('-f%s', '', $extraParams); 290 } else { 291 $extraParams = sprintf($extraParams, $reversePath); 292 } 293 } 294 295 return !empty($extraParams) ? $extraParams : null; 296 } 297} 298