Overview

Packages

  • Mime

Classes

  • Horde_Mime
  • Horde_Mime_Address
  • Horde_Mime_Exception
  • Horde_Mime_Headers
  • Horde_Mime_Magic
  • Horde_Mime_Mail
  • Horde_Mime_Mdn
  • Horde_Mime_Part
  • Horde_Mime_Translation
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * The Horde_Mime_Mdn:: class implements Message Disposition Notifications as
  4:  * described by RFC 3798.
  5:  *
  6:  * Copyright 2004-2012 Horde LLC (http://www.horde.org/)
  7:  *
  8:  * See the enclosed file COPYING for license information (LGPL). If you
  9:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 10:  *
 11:  * @author   Michael Slusarz <slusarz@horde.org>
 12:  * @category Horde
 13:  * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
 14:  * @package  Mime
 15:  */
 16: class Horde_Mime_Mdn
 17: {
 18:     /* RFC 3798 header for requesting a MDN. */
 19:     const MDN_HEADER = 'Disposition-Notification-To';
 20: 
 21:     /**
 22:      * The Horde_Mime_Headers object.
 23:      *
 24:      * @var Horde_Mime_Headers
 25:      */
 26:     protected $_headers;
 27: 
 28:     /**
 29:      * The text of the original message.
 30:      *
 31:      * @var string
 32:      */
 33:     protected $_msgtext = false;
 34: 
 35:     /**
 36:      * Constructor.
 37:      *
 38:      * @param Horde_Mime_Headers $mime_headers  A headers object.
 39:      */
 40:     public function __construct(Horde_Mime_Headers $headers)
 41:     {
 42:         $this->_headers = $headers;
 43:     }
 44: 
 45:     /**
 46:      * Returns the address to return the MDN to.
 47:      *
 48:      * @return string  The address to send the MDN to. Returns null if no
 49:      *                 MDN is requested.
 50:      */
 51:     public function getMdnReturnAddr()
 52:     {
 53:         /* RFC 3798 [2.1] requires the Disposition-Notification-To header
 54:          * for an MDN to be created. */
 55:         return $this->_headers->getValue(self::MDN_HEADER);
 56:     }
 57: 
 58:     /**
 59:      * Is user input required to send the MDN?
 60:      * Explicit confirmation is needed in some cases to prevent mail loops
 61:      * and the use of MDNs for mail bombing.
 62:      *
 63:      * @return boolean  Is explicit user input required to send the MDN?
 64:      */
 65:     public function userConfirmationNeeded()
 66:     {
 67:         $return_path = $this->_headers->getValue('Return-Path');
 68: 
 69:         /* RFC 3798 [2.1]: Explicit confirmation is needed if there is no
 70:          * Return-Path in the header. Also, "if the message contains more
 71:          * than one Return-Path header, the implementation may [] treat the
 72:          * situation as a failure of the comparison." */
 73:         if (empty($return_path) || is_array($return_path)) {
 74:             return true;
 75:         }
 76: 
 77:         /* RFC 3798 [2.1]: Explicit confirmation is needed if there is more
 78:          * than one distinct address in the Disposition-Notification-To
 79:          * header. */
 80:         try {
 81:             $addr_arr = Horde_Mime_Address::parseAddressList($this->getMdnReturnAddr());
 82:         } catch (Horde_Mime_Exception $e) {
 83:             return false;
 84:         }
 85: 
 86:         if (count($addr_arr) > 1) {
 87:             return true;
 88:         }
 89: 
 90:         /* RFC 3798 [2.1] states that "MDNs SHOULD NOT be sent automatically
 91:          * if the address in the Disposition-Notification-To header differs
 92:          * from the address in the Return-Path header." This comparison is
 93:          * case-sensitive for the mailbox part and case-insensitive for the
 94:          * host part. */
 95:         try {
 96:             $ret_arr = Horde_Mime_Address::parseAddressList($return_path);
 97:         } catch (Horde_Mime_Exception $e) {
 98:             return false;
 99:         }
100: 
101:         return ($addr_arr[0]['mailbox'] == $ret_arr[0]['mailbox']) &&
102:                (Horde_String::lower($addr_arr[0]['host']) == Horde_String::lower($ret_arr[0]['host']));
103:     }
104: 
105:     /**
106:      * When generating the MDN, should we return the enitre text of the
107:      * original message?  The default is no - we only return the headers of
108:      * the original message. If the text is passed in via this method, we
109:      * will return the entire message.
110:      *
111:      * @param string $text  The text of the original message.
112:      */
113:     public function originalMessageText($text)
114:     {
115:         $this->_msgtext = $text;
116:     }
117: 
118:     /**
119:      * Generate the MDN according to the specifications listed in RFC
120:      * 3798 [3].
121:      *
122:      * @param boolean $action   Was this MDN type a result of a manual
123:      *                          action on part of the user?
124:      * @param boolean $sending  Was this MDN sent as a result of a manual
125:      *                          action on part of the user?
126:      * @param string $type      The type of action performed by the user.
127:      * <pre>
128:      * Per RFC 3798 [3.2.6.2] the following types are valid:
129:      * 'displayed'
130:      * 'deleted'
131:      * </pre>
132:      * @param string $name      The name of the local server.
133:      * @param Mail $mailer      A Mail driver.
134:      * @param array $opts       Additional options:
135:      * <pre>
136:      * 'charset' - (string) Default charset.
137:      *             DEFAULT: NONE
138:      * 'from_addr' - (string) From address.
139:      *               DEFAULT: NONE
140:      * </pre>
141:      * @param array $mod        The list of modifications.
142:      * <pre>
143:      * Per RFC 3798 [3.2.6.3] the following modifications are valid:
144:      * 'error'
145:      * </pre>
146:      * @param array $err        If $mod is 'error', the additional
147:      *                          information to provide.  Key is the type of
148:      *                          modification, value is the text.
149:      *
150:      * @throws Horde_Mime_Exception
151:      */
152:     public function generate($action, $sending, $type, $name, $mailer,
153:                              array $opts = array(), array $mod = array(),
154:                              array $err = array())
155:     {
156:         $opts = array_merge(array(
157:             'charset' => null,
158:             'from_addr' => null
159:         ), $opts);
160: 
161:         $to = $this->getMdnReturnAddr();
162:         $ua = $this->_headers->getUserAgent();
163: 
164:         $orig_recip = $this->_headers->getValue('Original-Recipient');
165:         if (!empty($orig_recip) && is_array($orig_recip)) {
166:             $orig_recip = $orig_recip[0];
167:         }
168: 
169:         $msg_id = $this->_headers->getValue('Message-ID');
170: 
171:         /* Create the Disposition field now (RFC 3798 [3.2.6]). */
172:         $dispo = 'Disposition: ' .
173:                  (($action) ? 'manual-action' : 'automatic-action') .
174:                  '/' .
175:                  (($sending) ? 'MDN-sent-manually' : 'MDN-sent-automatically') .
176:                  '; ' .
177:                  $type;
178:         if (!empty($mod)) {
179:             $dispo .= '/' . implode(', ', $mod);
180:         }
181: 
182:         /* Set up the mail headers. */
183:         $msg_headers = new Horde_Mime_Headers();
184:         $msg_headers->addMessageIdHeader();
185:         $msg_headers->addUserAgentHeader($ua);
186:         $msg_headers->addHeader('Date', date('r'));
187:         if ($opts['from_addr']) {
188:             $msg_headers->addHeader('From', $opts['from_addr']);
189:         }
190:         $msg_headers->addHeader('To', $this->getMdnReturnAddr());
191:         $msg_headers->addHeader('Subject', Horde_Mime_Translation::t("Disposition Notification"));
192: 
193:         /* MDNs are a subtype of 'multipart/report'. */
194:         $msg = new Horde_Mime_Part();
195:         $msg->setType('multipart/report');
196:         $msg->setContentTypeParameter('report-type', 'disposition-notification');
197: 
198:         /* The first part is a human readable message. */
199:         $part_one = new Horde_Mime_Part();
200:         $part_one->setType('text/plain');
201:         $part_one->setCharset($opts['charset']);
202:         if ($type == 'displayed') {
203:             $contents = sprintf(Horde_Mime_Translation::t("The message sent on %s to %s with subject \"%s\" has been displayed.\n\nThis is no guarantee that the message has been read or understood."), $this->_headers->getValue('Date'), $this->_headers->getValue('To'), $this->_headers->getValue('Subject'));
204:             $flowed = new Horde_Text_Flowed($contents, $opts['charset']);
205:             $flowed->setDelSp(true);
206:             $part_one->setContentTypeParameter('format', 'flowed');
207:             $part_one->setContentTypeParameter('DelSp', 'Yes');
208:             $part_one->setContents($flowed->toFlowed());
209:         }
210:         // TODO: Messages for other notification types.
211:         $msg->addPart($part_one);
212: 
213:         /* The second part is a machine-parseable description. */
214:         $part_two = new Horde_Mime_Part();
215:         $part_two->setType('message/disposition-notification');
216:         $part_two_text = array('Reporting-UA: ' . $name . '; ' . $ua . "\n");
217:         if (!empty($orig_recip)) {
218:             $part_two_text[] = 'Original-Recipient: rfc822;' . $orig_recip . "\n";
219:         }
220:         if ($opts['from_addr']) {
221:             $part_two_text[] = 'Final-Recipient: rfc822;' . $opts['from_addr'] . "\n";
222:         }
223:         if (!empty($msg_id)) {
224:             $part_two_text[] = 'Original-Message-ID: rfc822;' . $msg_id . "\n";
225:         }
226:         $part_two_text[] = $dispo . "\n";
227:         if (in_array('error', $mod) && isset($err['error'])) {
228:             $part_two_text[] = 'Error: ' . $err['error'] . "\n";
229:         }
230:         $part_two->setContents($part_two_text);
231:         $msg->addPart($part_two);
232: 
233:         /* The third part is the text of the original message.  RFC 3798 [3]
234:          * allows us to return only a portion of the entire message - this
235:          * is left up to the user. */
236:         $part_three = new Horde_Mime_Part();
237:         $part_three->setType('message/rfc822');
238:         $part_three_text = array($this->_headers->toString());
239:         if (!empty($this->_msgtext)) {
240:             $part_three_text[] = $part_three->getEOL() . $this->_msgtext;
241:         }
242:         $part_three->setContents($part_three_text);
243:         $msg->addPart($part_three);
244: 
245:         return $msg->send($to, $msg_headers, $mailer);
246:     }
247: 
248:     /**
249:      * Add a MDN (read receipt) request headers to the Horde_Mime_Headers::
250:      * object.
251:      *
252:      * @param string $to  The address the receipt should be mailed to.
253:      */
254:     public function addMdnRequestHeaders($to)
255:     {
256:         /* This is the RFC 3798 way of requesting a receipt. */
257:         $this->_headers->addHeader(self::MDN_HEADER, $to);
258:     }
259: 
260: }
261: 
API documentation generated by ApiGen