1: <?php
2: /**
3: * Mail transport base class.
4: *
5: * LICENSE:
6: *
7: * Copyright (c) 2002-2007, Richard Heyes
8: * All rights reserved.
9: *
10: * Redistribution and use in source and binary forms, with or without
11: * modification, are permitted provided that the following conditions
12: * are met:
13: *
14: * o Redistributions of source code must retain the above copyright
15: * notice, this list of conditions and the following disclaimer.
16: * o Redistributions in binary form must reproduce the above copyright
17: * notice, this list of conditions and the following disclaimer in the
18: * documentation and/or other materials provided with the distribution.
19: * o The names of the authors may not be used to endorse or promote
20: * products derived from this software without specific prior written
21: * permission.
22: *
23: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34: *
35: * @category Mail
36: * @package Mail
37: * @author Chuck Hagenbuch <chuck@horde.org>
38: * @author Michael Slusarz <slusarz@horde.org>
39: * @copyright 1997-2010 Chuck Hagenbuch
40: * @copyright 2010 Michael Slusarz
41: * @license http://www.horde.org/licenses/bsd New BSD License
42: */
43:
44: /**
45: * Mail transport base class.
46: *
47: * @access public
48: * @version $Revision: 294747 $
49: * @package Mail
50: */
51: abstract class Horde_Mail_Transport
52: {
53: /**
54: * Line terminator used for separating header lines.
55: *
56: * @var string
57: */
58: public $sep = PHP_EOL;
59:
60: /**
61: * Configuration parameters.
62: *
63: * @var array
64: */
65: protected $_params = array();
66:
67: /**
68: * Send a message.
69: *
70: * @param mixed $recipients Either a comma-seperated list of recipients
71: * (RFC822 compliant), or an array of
72: * recipients, each RFC822 valid. This may
73: * contain recipients not specified in the
74: * headers, for Bcc:, resending messages, etc.
75: * @param array $headers The headers to send with the mail, in an
76: * associative array, where the array key is the
77: * header name (ie, 'Subject'), and the array
78: * value is the header value (ie, 'test'). The
79: * header produced from those values would be
80: * 'Subject: test'.
81: * If the '_raw' key exists, the value of this
82: * key will be used as the exact text for
83: * sending the message.
84: * @param mixed $body The full text of the message body, including
85: * any Mime parts, etc. Either a string or a
86: * stream resource.
87: *
88: * @throws Horde_Mail_Exception
89: */
90: abstract public function send($recipients, array $headers, $body);
91:
92: /**
93: * Take an array of mail headers and return a string containing text
94: * usable in sending a message.
95: *
96: * @param array $headers The array of headers to prepare, in an
97: * associative array, where the array key is the
98: * header name (ie, 'Subject'), and the array value
99: * is the header value (ie, 'test'). The header
100: * produced from those values would be 'Subject:
101: * test'.
102: * If the '_raw' key exists, the value of this key
103: * will be used as the exact text for sending the
104: * message.
105: *
106: * @return mixed Returns false if it encounters a bad address; otherwise
107: * returns an array containing two elements: Any From:
108: * address found in the headers, and the plain text version
109: * of the headers.
110: * @throws Horde_Mail_Exception
111: */
112: public function prepareHeaders(array $headers)
113: {
114: $from = null;
115: $lines = array();
116: $raw = isset($headers['_raw'])
117: ? $headers['_raw']
118: : null;
119:
120: $parser = new Horde_Mail_Rfc822();
121:
122: foreach ($headers as $key => $value) {
123: if (strcasecmp($key, 'From') === 0) {
124: $addresses = $parser->parseAddressList($value, array(
125: 'nest_groups' => false,
126: ));
127: $from = $addresses[0]->full_address;
128:
129: // Reject envelope From: addresses with spaces.
130: if (strstr($from, ' ')) {
131: return false;
132: }
133:
134: $lines[] = $key . ': ' . $this->_normalizeEOL($value);
135: } elseif (!$raw && (strcasecmp($key, 'Received') === 0)) {
136: $received = array();
137: if (!is_array($value)) {
138: $value = array($value);
139: }
140:
141: foreach ($value as $line) {
142: $received[] = $key . ': ' . $this->_normalizeEOL($line);
143: }
144:
145: // Put Received: headers at the top. Spam detectors often
146: // flag messages with Received: headers after the Subject:
147: // as spam.
148: $lines = array_merge($received, $lines);
149: } elseif (!$raw) {
150: // If $value is an array (i.e., a list of addresses), convert
151: // it to a comma-delimited string of its elements (addresses).
152: if (is_array($value)) {
153: $value = implode(', ', $value);
154: }
155: $lines[] = $key . ': ' . $this->_normalizeEOL($value);
156: }
157: }
158:
159: return array($from, $raw ? $raw : implode($this->sep, $lines));
160: }
161:
162: /**
163: * Take a set of recipients and parse them, returning an array of bare
164: * addresses (forward paths) that can be passed to sendmail or an SMTP
165: * server with the 'RCPT TO:' command.
166: *
167: * @param mixed Either a comma-separated list of recipients (RFC822
168: * compliant), or an array of recipients, each RFC822 valid.
169: *
170: * @return array Forward paths (bare addresses).
171: * @throws Horde_Mail_Exception
172: */
173: public function parseRecipients($recipients)
174: {
175: // if we're passed an array, assume addresses are valid and
176: // implode them before parsing.
177: if (is_array($recipients)) {
178: $recipients = implode(', ', $recipients);
179: }
180:
181: // Parse recipients, leaving out all personal info. This is
182: // for smtp recipients, etc. All relevant personal information
183: // should already be in the headers.
184: $parser = new Horde_Mail_Rfc822();
185: $addresses = $parser->parseAddressList($recipients, array(
186: 'nest_groups' => false
187: ));
188:
189: $recipients = array();
190:
191: foreach ($addresses as $ob) {
192: $recipients[] = $ob->full_address;
193: }
194:
195: return $recipients;
196: }
197:
198: /**
199: * Sanitize an array of mail headers by removing any additional header
200: * strings present in a legitimate header's value. The goal of this
201: * filter is to prevent mail injection attacks.
202: *
203: * Raw headers are sent as-is.
204: *
205: * @param array $headers The associative array of headers to sanitize.
206: *
207: * @return array The sanitized headers.
208: */
209: protected function _sanitizeHeaders($headers)
210: {
211: foreach (array_diff(array_keys($headers), array('_raw')) as $key) {
212: $headers[$key] = preg_replace('=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', null, $headers[$key]);
213: }
214:
215: return $headers;
216: }
217:
218: /**
219: * Normalizes EOLs in string data.
220: *
221: * @param string $data Data.
222: *
223: * @return string Normalized data.
224: */
225: protected function _normalizeEOL($data)
226: {
227: return strtr($data, array(
228: "\r\n" => $this->sep,
229: "\r" => $this->sep,
230: "\n" => $this->sep
231: ));
232: }
233:
234: }
235: