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