* @author Richard Heyes * @author Michael Slusarz * @category Horde * @copyright 1997-2017 Horde LLC (http://www.horde.org/) * @copyright 2002-2007 Richard Heyes * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail * * @property-read boolean $eai Does the transport driver support EAI (RFC * 6532) headers? (@since 2.5.0) */ abstract class Horde_Mail_Transport { /** * Line terminator used for separating header lines. * * @var string */ public $sep = PHP_EOL; /** * Configuration parameters. * * @var array */ protected $_params = array(); /** */ public function __get($name) { switch ($name) { case 'eai': return false; } } /** * Send a message. * * @param mixed $recipients Either a comma-separated list of recipients * (RFC822 compliant), or an array of * recipients, each RFC822 valid. This may * contain recipients not specified in the * headers, for Bcc:, resending messages, etc. * @param array $headers The headers to send with the mail, in an * associative array, where the array key is the * header name (ie, 'Subject'), and the array * value is the header value (ie, 'test'). The * header produced from those values would be * 'Subject: test'. * If the '_raw' key exists, the value of this * key will be used as the exact text for * sending the message. * @param mixed $body The full text of the message body, including * any Mime parts, etc. Either a string or a * stream resource. * * @throws Horde_Mail_Exception */ abstract public function send($recipients, array $headers, $body); /** * Take an array of mail headers and return a string containing text * usable in sending a message. * * @param array $headers The array of headers to prepare, in an * associative array, where the array key is the * header name (ie, 'Subject'), and the array value * is the header value (ie, 'test'). The header * produced from those values would be 'Subject: * test'. * If the '_raw' key exists, the value of this key * will be used as the exact text for sending the * message. * * @return mixed Returns false if it encounters a bad address; otherwise * returns an array containing two elements: Any From: * address found in the headers, and the plain text version * of the headers. * @throws Horde_Mail_Exception */ public function prepareHeaders(array $headers) { $from = null; $lines = array(); $raw = isset($headers['_raw']) ? $headers['_raw'] : null; foreach ($headers as $key => $value) { if (strcasecmp($key, 'From') === 0) { $parser = new Horde_Mail_Rfc822(); $addresses = $parser->parseAddressList($value, array( 'validate' => $this->eai ? 'eai' : true )); $from = $addresses[0]->bare_address; // Reject envelope From: addresses with spaces. if (strstr($from, ' ')) { return false; } $lines[] = $key . ': ' . $this->_normalizeEOL($value); } elseif (!$raw && (strcasecmp($key, 'Received') === 0)) { $received = array(); if (!is_array($value)) { $value = array($value); } foreach ($value as $line) { $received[] = $key . ': ' . $this->_normalizeEOL($line); } // Put Received: headers at the top. Spam detectors often // flag messages with Received: headers after the Subject: // as spam. $lines = array_merge($received, $lines); } elseif (!$raw) { // If $value is an array (i.e., a list of addresses), convert // it to a comma-delimited string of its elements (addresses). if (is_array($value)) { $value = implode(', ', $value); } $lines[] = $key . ': ' . $this->_normalizeEOL($value); } } return array($from, $raw ? $raw : implode($this->sep, $lines)); } /** * Take a set of recipients and parse them, returning an array of bare * addresses (forward paths) that can be passed to sendmail or an SMTP * server with the 'RCPT TO:' command. * * @param mixed $recipients Either a comma-separated list of recipients * (RFC822 compliant), or an array of * recipients, each RFC822 valid. * * @return array Forward paths (bare addresses, IDN encoded). * @throws Horde_Mail_Exception */ public function parseRecipients($recipients) { // Parse recipients, leaving out all personal info. This is // for smtp recipients, etc. All relevant personal information // should already be in the headers. $rfc822 = new Horde_Mail_Rfc822(); return $rfc822->parseAddressList($recipients, array( 'validate' => $this->eai ? 'eai' : true ))->bare_addresses_idn; } /** * Sanitize an array of mail headers by removing any additional header * strings present in a legitimate header's value. The goal of this * filter is to prevent mail injection attacks. * * Raw headers are sent as-is. * * @param array $headers The associative array of headers to sanitize. * * @return array The sanitized headers. */ protected function _sanitizeHeaders($headers) { foreach (array_diff(array_keys($headers), array('_raw')) as $key) { $headers[$key] = preg_replace('=((||0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', null, $headers[$key]); } return $headers; } /** * Normalizes EOLs in string data. * * @param string $data Data. * * @return string Normalized data. */ protected function _normalizeEOL($data) { return strtr($data, array( "\r\n" => $this->sep, "\r" => $this->sep, "\n" => $this->sep )); } /** * Get the from address. * * @param string $from From address. * @param array $headers Headers array. * * @return string Address object. * @throws Horde_Mail_Exception */ protected function _getFrom($from, $headers) { /* Since few MTAs are going to allow this header to be forged unless * it's in the MAIL FROM: exchange, we'll use Return-Path instead of * From: if it's set. */ foreach (array_keys($headers) as $hdr) { if (strcasecmp($hdr, 'Return-Path') === 0) { $from = $headers[$hdr]; break; } } if (!strlen($from)) { throw new Horde_Mail_Exception('No from address provided.'); } $from = new Horde_Mail_Rfc822_Address($from); return $from->bare_address_idn; } }