Changeset d311d80 in github for program/include/rcube_message.php
- Timestamp:
- May 28, 2010 5:38:41 AM (3 years ago)
- Branches:
- master, HEAD, courier-fix, dev-browser-capabilities, pdo, release-0.6, release-0.7, release-0.8
- Children:
- b231f68b
- Parents:
- 89e31be
- File:
-
- 1 edited
-
program/include/rcube_message.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
program/include/rcube_message.php
r6b6f2e8 rd311d80 30 30 class rcube_message 31 31 { 32 private $app;33 private $imap;34 private $opt = array();35 private $inline_parts = array();36 private $parse_alternative = false;32 private $app; 33 private $imap; 34 private $opt = array(); 35 private $inline_parts = array(); 36 private $parse_alternative = false; 37 37 38 public $uid = null;39 public $headers;40 public $structure;41 public $parts = array();42 public $mime_parts = array();43 public $attachments = array();44 public $subject = '';45 public $sender = null;46 public $is_safe = false;38 public $uid = null; 39 public $headers; 40 public $structure; 41 public $parts = array(); 42 public $mime_parts = array(); 43 public $attachments = array(); 44 public $subject = ''; 45 public $sender = null; 46 public $is_safe = false; 47 47 48 48 49 /**50 * __construct51 *52 * Provide a uid, and parse message structure.53 *54 * @param string $uid The message UID.55 *56 * @uses rcmail::get_instance()57 * @uses rcube_imap::decode_mime_string()58 * @uses self::set_safe()59 *60 * @see self::$app, self::$imap, self::$opt, self::$structure61 */62 function __construct($uid)63 {64 $this->app = rcmail::get_instance();65 $this->imap = $this->app->imap;49 /** 50 * __construct 51 * 52 * Provide a uid, and parse message structure. 53 * 54 * @param string $uid The message UID. 55 * 56 * @uses rcmail::get_instance() 57 * @uses rcube_imap::decode_mime_string() 58 * @uses self::set_safe() 59 * 60 * @see self::$app, self::$imap, self::$opt, self::$structure 61 */ 62 function __construct($uid) 63 { 64 $this->app = rcmail::get_instance(); 65 $this->imap = $this->app->imap; 66 66 67 $this->uid = $uid; 68 $this->headers = $this->imap->get_headers($uid, NULL, true, true); 69 70 $this->subject = rcube_imap::decode_mime_string($this->headers->subject, $this->headers->charset); 71 list(, $this->sender) = each($this->imap->decode_address_list($this->headers->from)); 67 $this->uid = $uid; 68 $this->headers = $this->imap->get_headers($uid, NULL, true, true); 69 70 $this->subject = rcube_imap::decode_mime_string( 71 $this->headers->subject, $this->headers->charset); 72 list(, $this->sender) = each($this->imap->decode_address_list($this->headers->from)); 72 73 73 $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$uid])); 74 $this->opt = array( 75 'safe' => $this->is_safe, 76 'prefer_html' => $this->app->config->get('prefer_html'), 77 'get_url' => rcmail_url('get', array('_mbox' => $this->imap->get_mailbox_name(), '_uid' => $uid)) 78 ); 79 80 if ($this->structure = $this->imap->get_structure($uid, $this->headers->body_structure)) { 81 $this->get_mime_numbers($this->structure); 82 $this->parse_structure($this->structure); 83 } 84 else { 85 $this->body = $this->imap->get_body($uid); 86 } 87 88 // notify plugins and let them analyze this structured message object 89 $this->app->plugins->exec_hook('message_load', array('object' => $this)); 90 } 74 $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$uid])); 75 $this->opt = array( 76 'safe' => $this->is_safe, 77 'prefer_html' => $this->app->config->get('prefer_html'), 78 'get_url' => rcmail_url('get', array( 79 '_mbox' => $this->imap->get_mailbox_name(), '_uid' => $uid)) 80 ); 81 82 if ($this->structure = $this->imap->get_structure($uid, $this->headers->body_structure)) { 83 $this->get_mime_numbers($this->structure); 84 $this->parse_structure($this->structure); 85 } 86 else { 87 $this->body = $this->imap->get_body($uid); 88 } 89 90 // notify plugins and let them analyze this structured message object 91 $this->app->plugins->exec_hook('message_load', array('object' => $this)); 92 } 91 93 92 94 93 /** 94 * Return a (decoded) message header 95 * 96 * @param string Header name 97 * @param bool Don't mime-decode the value 98 * @return string Header value 99 */ 100 public function get_header($name, $raw = false) 101 { 102 $value = $this->headers->$name; 103 return $raw ? $value : $this->imap->decode_header($value); 104 } 95 /** 96 * Return a (decoded) message header 97 * 98 * @param string Header name 99 * @param bool Don't mime-decode the value 100 * @return string Header value 101 */ 102 public function get_header($name, $raw = false) 103 { 104 $value = $this->headers->$name; 105 return $raw ? $value : $this->imap->decode_header($value); 106 } 107 105 108 106 /** 107 * Set is_safe var and session data 108 * 109 * @param bool enable/disable 110 */ 111 public function set_safe($safe = true) 112 { 113 $this->is_safe = $safe; 114 $_SESSION['safe_messages'][$this->uid] = $this->is_safe; 115 } 116 117 /** 118 * Compose a valid URL for getting a message part 119 * 120 * @param string Part MIME-ID 121 * @return string URL or false if part does not exist 122 */ 123 public function get_part_url($mime_id) 124 { 125 if ($this->mime_parts[$mime_id]) 126 return $this->opt['get_url'] . "&_part=" . $mime_id; 127 else 128 return false; 129 } 130 131 132 /** 133 * Get content of a specific part of this message 134 * 135 * @param string Part MIME-ID 136 * @param resource File pointer to save the message part 137 * @return string Part content 138 */ 139 public function get_part_content($mime_id, $fp=NULL) 140 { 141 if ($part = $this->mime_parts[$mime_id]) 142 return $this->imap->get_message_part($this->uid, $mime_id, $part, NULL, $fp); 143 else 144 return null; 145 } 146 147 148 /** 149 * Determine if the message contains a HTML part 150 * 151 * @return bool True if a HTML is available, False if not 152 */ 153 function has_html_part() 154 { 155 // check all message parts 156 foreach ($this->parts as $pid => $part) { 157 $mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary); 158 if ($mimetype == 'text/html') 159 return true; 160 } 161 162 return false; 163 } 164 165 /** 166 * Return the first HTML part of this message 167 * 168 * @return string HTML message part content 169 */ 170 function first_html_part() 171 { 172 // check all message parts 173 foreach ($this->mime_parts as $mime_id => $part) { 174 $mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary); 175 if ($mimetype == 'text/html') { 176 return $this->imap->get_message_part($this->uid, $mime_id, $part); 177 } 178 } 179 } 180 181 182 /** 183 * Return the first text part of this message 184 * 185 * @return string Plain text message/part content 186 */ 187 function first_text_part() 188 { 189 // no message structure, return complete body 190 if (empty($this->parts)) 191 return $this->body; 109 /** 110 * Set is_safe var and session data 111 * 112 * @param bool enable/disable 113 */ 114 public function set_safe($safe = true) 115 { 116 $this->is_safe = $safe; 117 $_SESSION['safe_messages'][$this->uid] = $this->is_safe; 118 } 119 120 121 /** 122 * Compose a valid URL for getting a message part 123 * 124 * @param string Part MIME-ID 125 * @return string URL or false if part does not exist 126 */ 127 public function get_part_url($mime_id) 128 { 129 if ($this->mime_parts[$mime_id]) 130 return $this->opt['get_url'] . '&_part=' . $mime_id; 131 else 132 return false; 133 } 134 135 136 /** 137 * Get content of a specific part of this message 138 * 139 * @param string Part MIME-ID 140 * @param resource File pointer to save the message part 141 * @return string Part content 142 */ 143 public function get_part_content($mime_id, $fp=NULL) 144 { 145 if ($part = $this->mime_parts[$mime_id]) { 146 // stored in message structure (winmail/inline-uuencode) 147 if ($part->encoding == 'stream') { 148 if ($fp) { 149 fwrite($fp, $part->body); 150 } 151 return $fp ? true : $part->body; 152 } 153 // get from IMAP 154 return $this->imap->get_message_part($this->uid, $mime_id, $part, NULL, $fp); 155 } else 156 return null; 157 } 158 159 160 /** 161 * Determine if the message contains a HTML part 162 * 163 * @return bool True if a HTML is available, False if not 164 */ 165 function has_html_part() 166 { 167 // check all message parts 168 foreach ($this->parts as $pid => $part) { 169 $mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary); 170 if ($mimetype == 'text/html') 171 return true; 172 } 173 174 return false; 175 } 176 177 178 /** 179 * Return the first HTML part of this message 180 * 181 * @return string HTML message part content 182 */ 183 function first_html_part() 184 { 185 // check all message parts 186 foreach ($this->mime_parts as $mime_id => $part) { 187 $mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary); 188 if ($mimetype == 'text/html') { 189 return $this->imap->get_message_part($this->uid, $mime_id, $part); 190 } 191 } 192 } 193 194 195 /** 196 * Return the first text part of this message 197 * 198 * @return string Plain text message/part content 199 */ 200 function first_text_part() 201 { 202 // no message structure, return complete body 203 if (empty($this->parts)) 204 return $this->body; 205 206 $out = null; 207 208 // check all message parts 209 foreach ($this->mime_parts as $mime_id => $part) { 210 $mimetype = $part->ctype_primary . '/' . $part->ctype_secondary; 211 212 if ($mimetype == 'text/plain') { 213 $out = $this->imap->get_message_part($this->uid, $mime_id, $part); 214 215 // re-format format=flowed content 216 if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') 217 $out = self::unfold_flowed($out); 218 break; 219 } 220 else if ($mimetype == 'text/html') { 221 $html_part = $this->imap->get_message_part($this->uid, $mime_id, $part); 222 223 // remove special chars encoding 224 $trans = array_flip(get_html_translation_table(HTML_ENTITIES)); 225 $html_part = strtr($html_part, $trans); 226 227 // create instance of html2text class 228 $txt = new html2text($html_part); 229 $out = $txt->get_text(); 230 break; 231 } 232 } 233 234 return $out; 235 } 236 237 238 /** 239 * Raad the message structure returend by the IMAP server 240 * and build flat lists of content parts and attachments 241 * 242 * @param object rcube_message_part Message structure node 243 * @param bool True when called recursively 244 */ 245 private function parse_structure($structure, $recursive = false) 246 { 247 $message_ctype_primary = $structure->ctype_primary; 248 $message_ctype_secondary = $structure->ctype_secondary; 249 $mimetype = $structure->mimetype; 250 251 // real content-type of message/rfc822 part 252 if ($mimetype == 'message/rfc822') { 253 if ($structure->real_mimetype) { 254 $mimetype = $structure->real_mimetype; 255 list($message_ctype_primary, $message_ctype_secondary) = explode('/', $mimetype); 256 } 257 } 258 259 // show message headers 260 if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) { 261 $c = new stdClass; 262 $c->type = 'headers'; 263 $c->headers = &$structure->headers; 264 $this->parts[] = $c; 265 } 266 267 // print body if message doesn't have multiple parts 268 if ($message_ctype_primary == 'text' && !$recursive) { 269 $structure->type = 'content'; 270 $this->parts[] = &$structure; 271 272 // Parse simple (plain text) message body 273 if ($message_ctype_secondary == 'plain') 274 foreach ((array)$this->uu_decode($structure) as $uupart) { 275 $this->mime_parts[$uupart->mime_id] = $uupart; 276 $this->attachments[] = $uupart; 277 } 278 279 // @TODO: plugin hook? 280 } 281 // the same for pgp signed messages 282 else if ($mimetype == 'application/pgp' && !$recursive) { 283 $structure->type = 'content'; 284 $this->parts[] = &$structure; 285 } 286 // message contains alternative parts 287 else if ($mimetype == 'multipart/alternative' && is_array($structure->parts)) { 288 // get html/plaintext parts 289 $plain_part = $html_part = $print_part = $related_part = null; 290 291 foreach ($structure->parts as $p => $sub_part) { 292 $sub_mimetype = $sub_part->mimetype; 293 294 // check if sub part is 295 if ($sub_mimetype == 'text/plain') 296 $plain_part = $p; 297 else if ($sub_mimetype == 'text/html') 298 $html_part = $p; 299 else if ($sub_mimetype == 'text/enriched') 300 $enriched_part = $p; 301 else if (in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative'))) 302 $related_part = $p; 303 } 304 305 // parse related part (alternative part could be in here) 306 if ($related_part !== null && !$this->parse_alternative) { 307 $this->parse_alternative = true; 308 $this->parse_structure($structure->parts[$related_part], true); 309 $this->parse_alternative = false; 310 311 // if plain part was found, we should unset it if html is preferred 312 if ($this->opt['prefer_html'] && count($this->parts)) 313 $plain_part = null; 314 } 315 316 // choose html/plain part to print 317 if ($html_part !== null && $this->opt['prefer_html']) { 318 $print_part = &$structure->parts[$html_part]; 319 } 320 else if ($enriched_part !== null) { 321 $print_part = &$structure->parts[$enriched_part]; 322 } 323 else if ($plain_part !== null) { 324 $print_part = &$structure->parts[$plain_part]; 325 } 326 327 // add the right message body 328 if (is_object($print_part)) { 329 $print_part->type = 'content'; 330 $this->parts[] = $print_part; 331 } 332 // show plaintext warning 333 else if ($html_part !== null && empty($this->parts)) { 334 $c = new stdClass; 335 $c->type = 'content'; 336 $c->ctype_primary = 'text'; 337 $c->ctype_secondary = 'plain'; 338 $c->body = rcube_label('htmlmessage'); 339 340 $this->parts[] = $c; 341 } 342 343 // add html part as attachment 344 if ($html_part !== null && $structure->parts[$html_part] !== $print_part) { 345 $html_part = &$structure->parts[$html_part]; 346 $html_part->filename = rcube_label('htmlmessage'); 347 $html_part->mimetype = 'text/html'; 348 349 $this->attachments[] = $html_part; 350 } 351 } 352 // this is an ecrypted message -> create a plaintext body with the according message 353 else if ($mimetype == 'multipart/encrypted') { 354 $p = new stdClass; 355 $p->type = 'content'; 356 $p->ctype_primary = 'text'; 357 $p->ctype_secondary = 'plain'; 358 $p->body = rcube_label('encryptedmessage'); 359 $p->size = strlen($p->body); 192 360 193 $out = null; 194 195 // check all message parts 196 foreach ($this->mime_parts as $mime_id => $part) { 197 $mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary); 198 199 if ($mimetype == 'text/plain') { 200 $out = $this->imap->get_message_part($this->uid, $mime_id, $part); 361 // maybe some plugins are able to decode this encrypted message part 362 $data = $this->app->plugins->exec_hook('message_part_encrypted', 363 array('object' => $this, 'struct' => $structure, 'part' => $p)); 364 365 if (is_array($data['parts'])) { 366 $this->parts = array_merge($this->parts, $data['parts']); 367 } 368 else if ($data['part']) { 369 $this->parts[] = $p; 370 } 371 } 372 // message contains multiple parts 373 else if (is_array($structure->parts) && !empty($structure->parts)) { 374 // iterate over parts 375 for ($i=0; $i < count($structure->parts); $i++) { 376 $mail_part = &$structure->parts[$i]; 377 $primary_type = $mail_part->ctype_primary; 378 $secondary_type = $mail_part->ctype_secondary; 379 380 // real content-type of message/rfc822 381 if ($mail_part->real_mimetype) { 382 $part_orig_mimetype = $mail_part->mimetype; 383 $part_mimetype = $mail_part->real_mimetype; 384 list($primary_type, $secondary_type) = explode('/', $part_mimetype); 385 } 386 else 387 $part_mimetype = $mail_part->mimetype; 388 389 // multipart/alternative 390 if ($primary_type == 'multipart') { 391 $this->parse_structure($mail_part, true); 392 393 // list message/rfc822 as attachment as well (mostly .eml) 394 if ($part_orig_mimetype == 'message/rfc822' && !empty($mail_part->filename)) 395 $this->attachments[] = $mail_part; 396 } 397 // part text/[plain|html] OR message/delivery-status 398 else if ((($part_mimetype == 'text/plain' || $part_mimetype == 'text/html') && $mail_part->disposition != 'attachment') || 399 $part_mimetype == 'message/delivery-status' || $part_mimetype == 'message/disposition-notification' 400 ) { 401 // add text part if it matches the prefs 402 if (!$this->parse_alternative || 403 ($secondary_type == 'html' && $this->opt['prefer_html']) || 404 ($secondary_type == 'plain' && !$this->opt['prefer_html']) 405 ) { 406 $mail_part->type = 'content'; 407 $this->parts[] = $mail_part; 408 } 409 410 // list as attachment as well 411 if (!empty($mail_part->filename)) 412 $this->attachments[] = $mail_part; 413 } 414 // part message/* 415 else if ($primary_type=='message') { 416 $this->parse_structure($mail_part, true); 417 418 // list as attachment as well (mostly .eml) 419 if (!empty($mail_part->filename)) 420 $this->attachments[] = $mail_part; 421 } 422 // ignore "virtual" protocol parts 423 else if ($primary_type == 'protocol') { 424 continue; 425 } 426 // part is Microsoft Outlook TNEF (winmail.dat) 427 else if ($part_mimetype == 'application/ms-tnef') { 428 foreach ((array)$this->tnef_decode($mail_part) as $tpart) { 429 $this->mime_parts[$tpart->mime_id] = $tpart; 430 $this->attachments[] = $tpart; 431 } 432 } 433 // part is a file/attachment 434 else if (preg_match('/^(inline|attach)/', $mail_part->disposition) || 435 $mail_part->headers['content-id'] || (empty($mail_part->disposition) && $mail_part->filename) 436 ) { 437 // skip apple resource forks 438 if ($message_ctype_secondary == 'appledouble' && $secondary_type == 'applefile') 439 continue; 440 441 // part belongs to a related message and is linked 442 if ($mimetype == 'multipart/related' 443 && preg_match('!^image/!', $part_mimetype) 444 && ($mail_part->headers['content-id'] || $mail_part->headers['content-location'])) { 445 if ($mail_part->headers['content-id']) 446 $mail_part->content_id = preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']); 447 if ($mail_part->headers['content-location']) 448 $mail_part->content_location = $mail_part->headers['content-base'] . $mail_part->headers['content-location']; 449 450 $this->inline_parts[] = $mail_part; 451 } 452 // attachment encapsulated within message/rfc822 part needs further decoding (#1486743) 453 else if ($part_orig_mimetype == 'message/rfc822') { 454 $this->parse_structure($mail_part, true); 455 } 456 // is a regular attachment 457 else if (preg_match('!^[a-z0-9-.+]+/[a-z0-9-.+]+$!i', $part_mimetype)) { 458 if (!$mail_part->filename) 459 $mail_part->filename = 'Part '.$mail_part->mime_id; 460 $this->attachments[] = $mail_part; 461 } 462 } 463 } 464 465 // if this was a related part try to resolve references 466 if ($mimetype == 'multipart/related' && sizeof($this->inline_parts)) { 467 $a_replaces = array(); 468 469 foreach ($this->inline_parts as $inline_object) { 470 $part_url = $this->get_part_url($inline_object->mime_id); 471 if ($inline_object->content_id) 472 $a_replaces['cid:'.$inline_object->content_id] = $part_url; 473 if ($inline_object->content_location) 474 $a_replaces[$inline_object->content_location] = $part_url; 475 } 476 477 // add replace array to each content part 478 // (will be applied later when part body is available) 479 foreach ($this->parts as $i => $part) { 480 if ($part->type == 'content') 481 $this->parts[$i]->replaces = $a_replaces; 482 } 483 } 484 } 485 // message is a single part non-text 486 else if ($structure->filename) { 487 $this->attachments[] = $structure; 488 } 489 } 490 491 492 /** 493 * Fill aflat array with references to all parts, indexed by part numbers 494 * 495 * @param object rcube_message_part Message body structure 496 */ 497 private function get_mime_numbers(&$part) 498 { 499 if (strlen($part->mime_id)) 500 $this->mime_parts[$part->mime_id] = &$part; 501 502 if (is_array($part->parts)) 503 for ($i=0; $i<count($part->parts); $i++) 504 $this->get_mime_numbers($part->parts[$i]); 505 } 506 507 508 /** 509 * Decode a Microsoft Outlook TNEF part (winmail.dat) 510 * 511 * @param object rcube_message_part Message part to decode 512 */ 513 function tnef_decode(&$part) 514 { 515 // @TODO: attachment may be huge, hadle it via file 516 if (!isset($part->body)) 517 $part->body = $this->imap->get_message_part($this->uid, $part->mime_id, $part); 518 519 require_once('lib/tnef_decoder.inc'); 520 521 $parts = array(); 522 $tnef_arr = tnef_decode($part->body); 523 524 foreach ($tnef_arr as $pid => $winatt) { 525 $tpart = new rcube_message_part; 526 527 $tpart->filename = trim($winatt['name']); 528 $tpart->encoding = 'stream'; 529 $tpart->ctype_primary = trim(strtolower($winatt['type0'])); 530 $tpart->ctype_secondary = trim(strtolower($winatt['type1'])); 531 $tpart->mimetype = $tpart->ctype_primary . '/' . $tpart->ctype_secondary; 532 $tpart->mime_id = 'winmail.' . $part->mime_id . '.' . $pid; 533 $tpart->size = $winatt['size']; 534 $tpart->body = $winatt['stream']; 535 536 $parts[] = $tpart; 537 unset($tnef_arr[$pid]); 538 } 201 539 202 // re-format format=flowed content 203 if ($part->ctype_secondary == "plain" && $part->ctype_parameters['format'] == "flowed") 204 $out = self::unfold_flowed($out); 205 break; 206 } 207 else if ($mimetype == 'text/html') { 208 $html_part = $this->imap->get_message_part($this->uid, $mime_id, $part); 209 210 // remove special chars encoding 211 $trans = array_flip(get_html_translation_table(HTML_ENTITIES)); 212 $html_part = strtr($html_part, $trans); 213 214 // create instance of html2text class 215 $txt = new html2text($html_part); 216 $out = $txt->get_text(); 217 break; 218 } 219 } 220 221 return $out; 222 } 223 224 225 /** 226 * Raad the message structure returend by the IMAP server 227 * and build flat lists of content parts and attachments 228 * 229 * @param object rcube_message_part Message structure node 230 * @param bool True when called recursively 231 */ 232 private function parse_structure($structure, $recursive = false) 233 { 234 $message_ctype_primary = $structure->ctype_primary; 235 $message_ctype_secondary = $structure->ctype_secondary; 236 $mimetype = $structure->mimetype; 237 238 // real content-type of message/rfc822 part 239 if ($mimetype == 'message/rfc822') { 240 if ($structure->real_mimetype) { 241 $mimetype = $structure->real_mimetype; 242 list($message_ctype_primary, $message_ctype_secondary) = explode('/', $mimetype); 243 } 244 } 245 246 // show message headers 247 if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) { 248 $c = new stdClass; 249 $c->type = 'headers'; 250 $c->headers = &$structure->headers; 251 $this->parts[] = $c; 252 } 253 254 // print body if message doesn't have multiple parts 255 if ($message_ctype_primary == 'text' && !$recursive) { 256 $structure->type = 'content'; 257 $this->parts[] = &$structure; 258 } 259 // the same for pgp signed messages 260 else if ($mimetype == 'application/pgp' && !$recursive) { 261 $structure->type = 'content'; 262 $this->parts[] = &$structure; 263 } 264 // message contains alternative parts 265 else if ($mimetype == 'multipart/alternative' && is_array($structure->parts)) { 266 // get html/plaintext parts 267 $plain_part = $html_part = $print_part = $related_part = null; 268 269 foreach ($structure->parts as $p => $sub_part) { 270 $sub_mimetype = $sub_part->mimetype; 540 return $parts; 541 } 542 543 544 /** 545 * Parse message body for UUencoded attachments bodies 546 * 547 * @param object rcube_message_part Message part to decode 548 */ 549 function uu_decode(&$part) 550 { 551 // @TODO: messages may be huge, hadle body via file 552 if (!isset($part->body)) 553 $part->body = $this->imap->get_message_part($this->uid, $part->mime_id, $part); 554 555 $parts = array(); 556 // FIXME: line length is max.65? 557 $uu_regexp = '/begin [0-7]{3,4} ([^\n]+)\n(([\x21-\x7E]{0,65}\n)+)`\nend/s'; 558 559 if (preg_match_all($uu_regexp, $part->body, $matches, PREG_SET_ORDER)) { 560 // remove attachments bodies from the message body 561 $part->body = preg_replace($uu_regexp, '', $part->body); 562 // update message content-type 563 $part->ctype_primary = 'multipart'; 564 $part->ctype_secondary = 'mixed'; 565 $part->mimetype = $part->ctype_primary . '/' . $part->ctype_secondary; 566 567 // add attachments to the structure 568 foreach ($matches as $pid => $att) { 569 $uupart = new rcube_message_part; 570 571 $uupart->filename = trim($att[1]); 572 $uupart->encoding = 'stream'; 573 $uupart->body = convert_uudecode($att[2]); 574 $uupart->size = strlen($uupart->body); 575 $uupart->mime_id = 'uu.' . $part->mime_id . '.' . $pid; 576 577 $ctype = rc_mime_content_type($uupart->body, $uupart->filename, 'application/octet-stream', true); 578 $uupart->mimetype = $ctype; 579 list($uupart->ctype_primary, $uupart->ctype_secondary) = explode('/', $ctype); 580 581 $parts[] = $uupart; 582 unset($matches[$pid]); 583 } 584 } 271 585 272 // check if sub part is 273 if ($sub_mimetype == 'text/plain') 274 $plain_part = $p; 275 else if ($sub_mimetype == 'text/html') 276 $html_part = $p; 277 else if ($sub_mimetype == 'text/enriched') 278 $enriched_part = $p; 279 else if (in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative'))) 280 $related_part = $p; 281 } 282 283 // parse related part (alternative part could be in here) 284 if ($related_part !== null && !$this->parse_alternative) { 285 $this->parse_alternative = true; 286 $this->parse_structure($structure->parts[$related_part], true); 287 $this->parse_alternative = false; 288 289 // if plain part was found, we should unset it if html is preferred 290 if ($this->opt['prefer_html'] && count($this->parts)) 291 $plain_part = null; 292 } 293 294 // choose html/plain part to print 295 if ($html_part !== null && $this->opt['prefer_html']) { 296 $print_part = &$structure->parts[$html_part]; 297 } 298 else if ($enriched_part !== null) { 299 $print_part = &$structure->parts[$enriched_part]; 300 } 301 else if ($plain_part !== null) { 302 $print_part = &$structure->parts[$plain_part]; 303 } 304 305 // add the right message body 306 if (is_object($print_part)) { 307 $print_part->type = 'content'; 308 $this->parts[] = $print_part; 309 } 310 // show plaintext warning 311 else if ($html_part !== null && empty($this->parts)) { 312 $c = new stdClass; 313 $c->type = 'content'; 314 $c->body = rcube_label('htmlmessage'); 315 $c->ctype_primary = 'text'; 316 $c->ctype_secondary = 'plain'; 317 318 $this->parts[] = $c; 319 } 320 321 // add html part as attachment 322 if ($html_part !== null && $structure->parts[$html_part] !== $print_part) { 323 $html_part = &$structure->parts[$html_part]; 324 $html_part->filename = rcube_label('htmlmessage'); 325 $html_part->mimetype = 'text/html'; 326 327 $this->attachments[] = $html_part; 328 } 329 } 330 // this is an ecrypted message -> create a plaintext body with the according message 331 else if ($mimetype == 'multipart/encrypted') { 332 $p = new stdClass; 333 $p->type = 'content'; 334 $p->ctype_primary = 'text'; 335 $p->ctype_secondary = 'plain'; 336 $p->body = rcube_label('encryptedmessage'); 337 $p->size = strlen($p->body); 338 339 // maybe some plugins are able to decode this encrypted message part 340 $data = $this->app->plugins->exec_hook('message_part_encrypted', array('object' => $this, 'struct' => $structure, 'part' => $p)); 341 if (is_array($data['parts'])) { 342 $this->parts = array_merge($this->parts, $data['parts']); 343 } 344 else if ($data['part']) { 345 $this->parts[] = $p; 346 } 347 } 348 // message contains multiple parts 349 else if (is_array($structure->parts) && !empty($structure->parts)) { 350 // iterate over parts 351 for ($i=0; $i < count($structure->parts); $i++) { 352 $mail_part = &$structure->parts[$i]; 353 $primary_type = $mail_part->ctype_primary; 354 $secondary_type = $mail_part->ctype_secondary; 355 356 // real content-type of message/rfc822 357 if ($mail_part->real_mimetype) { 358 $part_orig_mimetype = $mail_part->mimetype; 359 $part_mimetype = $mail_part->real_mimetype; 360 list($primary_type, $secondary_type) = explode('/', $part_mimetype); 361 } 362 else 363 $part_mimetype = $mail_part->mimetype; 364 365 // multipart/alternative 366 if ($primary_type=='multipart') { 367 $this->parse_structure($mail_part, true); 368 369 // list message/rfc822 as attachment as well (mostly .eml) 370 if ($part_orig_mimetype == 'message/rfc822' && !empty($mail_part->filename)) 371 $this->attachments[] = $mail_part; 372 } 373 // part text/[plain|html] OR message/delivery-status 374 else if ((($part_mimetype == 'text/plain' || $part_mimetype == 'text/html') && $mail_part->disposition != 'attachment') || 375 $part_mimetype == 'message/delivery-status' || $part_mimetype == 'message/disposition-notification') { 376 377 // add text part if it matches the prefs 378 if (!$this->parse_alternative || 379 ($secondary_type == 'html' && $this->opt['prefer_html']) || 380 ($secondary_type == 'plain' && !$this->opt['prefer_html'])) { 381 $mail_part->type = 'content'; 382 $this->parts[] = $mail_part; 383 } 384 385 // list as attachment as well 386 if (!empty($mail_part->filename)) 387 $this->attachments[] = $mail_part; 388 } 389 // part message/* 390 else if ($primary_type=='message') { 391 $this->parse_structure($mail_part, true); 392 393 // list as attachment as well (mostly .eml) 394 if (!empty($mail_part->filename)) 395 $this->attachments[] = $mail_part; 396 } 397 // ignore "virtual" protocol parts 398 else if ($primary_type == 'protocol') 399 continue; 400 401 // part is Microsoft Outlook TNEF (winmail.dat) 402 else if ($part_mimetype == 'application/ms-tnef') { 403 foreach ((array)$this->imap->tnef_decode($mail_part, $structure->headers['uid']) as $tnef_part) { 404 $this->mime_parts[$tnef_part->mime_id] = $tnef_part; 405 $this->attachments[] = $tnef_part; 406 } 407 } 408 // part is a file/attachment 409 else if (preg_match('/^(inline|attach)/', $mail_part->disposition) || 410 $mail_part->headers['content-id'] || (empty($mail_part->disposition) && $mail_part->filename)) { 411 412 // skip apple resource forks 413 if ($message_ctype_secondary == 'appledouble' && $secondary_type == 'applefile') 414 continue; 415 416 // part belongs to a related message and is linked 417 if ($mimetype == 'multipart/related' 418 && preg_match('!^image/!', $part_mimetype) 419 && ($mail_part->headers['content-id'] || $mail_part->headers['content-location'])) { 420 if ($mail_part->headers['content-id']) 421 $mail_part->content_id = preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']); 422 if ($mail_part->headers['content-location']) 423 $mail_part->content_location = $mail_part->headers['content-base'] . $mail_part->headers['content-location']; 424 425 $this->inline_parts[] = $mail_part; 426 } 427 // attachment encapsulated within message/rfc822 part needs further decoding (#1486743) 428 else if ($part_orig_mimetype == 'message/rfc822') { 429 $this->parse_structure($mail_part, true); 430 } 431 // is a regular attachment 432 else if (preg_match('!^[a-z0-9-.+]+/[a-z0-9-.+]+$!i', $part_mimetype)) { 433 if (!$mail_part->filename) 434 $mail_part->filename = 'Part '.$mail_part->mime_id; 435 $this->attachments[] = $mail_part; 436 } 437 } 438 } 439 440 // if this was a related part try to resolve references 441 if ($mimetype == 'multipart/related' && sizeof($this->inline_parts)) { 442 $a_replaces = array(); 443 444 foreach ($this->inline_parts as $inline_object) { 445 $part_url = $this->get_part_url($inline_object->mime_id); 446 if ($inline_object->content_id) 447 $a_replaces['cid:'.$inline_object->content_id] = $part_url; 448 if ($inline_object->content_location) 449 $a_replaces[$inline_object->content_location] = $part_url; 450 } 451 452 // add replace array to each content part 453 // (will be applied later when part body is available) 454 foreach ($this->parts as $i => $part) { 455 if ($part->type == 'content') 456 $this->parts[$i]->replaces = $a_replaces; 457 } 458 } 459 } 460 461 // message is a single part non-text 462 else if ($structure->filename) { 463 $this->attachments[] = $structure; 464 } 465 } 466 467 468 /** 469 * Fill aflat array with references to all parts, indexed by part numbers 470 * 471 * @param object rcube_message_part Message body structure 472 */ 473 private function get_mime_numbers(&$part) 474 { 475 if (strlen($part->mime_id)) 476 $this->mime_parts[$part->mime_id] = &$part; 477 478 if (is_array($part->parts)) 479 for ($i=0; $i<count($part->parts); $i++) 480 $this->get_mime_numbers($part->parts[$i]); 481 } 482 483 484 /** 485 * Interpret a format=flowed message body according to RFC 2646 486 * 487 * @param string Raw body formatted as flowed text 488 * @return string Interpreted text with unwrapped lines and stuffed space removed 489 */ 490 public static function unfold_flowed($text) 491 { 492 return preg_replace( 493 array('/-- (\r?\n)/', '/^ /m', '/(.) \r?\n/', '/--%SIGEND%(\r?\n)/'), 494 array('--%SIGEND%\\1', '', '\\1 ', '-- \\1'), 495 $text); 496 } 497 498 /** 499 * Wrap the given text to comply with RFC 2646 500 */ 501 public static function format_flowed($text, $length = 72) 502 { 503 $out = ''; 586 return $parts; 587 } 588 589 590 /** 591 * Interpret a format=flowed message body according to RFC 2646 592 * 593 * @param string Raw body formatted as flowed text 594 * @return string Interpreted text with unwrapped lines and stuffed space removed 595 */ 596 public static function unfold_flowed($text) 597 { 598 return preg_replace( 599 array('/-- (\r?\n)/', '/^ /m', '/(.) \r?\n/', '/--%SIGEND%(\r?\n)/'), 600 array('--%SIGEND%\\1', '', '\\1 ', '-- \\1'), 601 $text); 602 } 603 604 605 /** 606 * Wrap the given text to comply with RFC 2646 607 */ 608 public static function format_flowed($text, $length = 72) 609 { 610 $out = ''; 504 611 505 foreach (preg_split('/\r?\n/', trim($text)) as $line) {506 // don't wrap quoted lines (to avoid wrapping problems)507 if ($line[0] != '>')508 $line = rc_wordwrap(rtrim($line), $length - 1, " \r\n");509 510 $out .= $line . "\r\n";511 }612 foreach (preg_split('/\r?\n/', trim($text)) as $line) { 613 // don't wrap quoted lines (to avoid wrapping problems) 614 if ($line[0] != '>') 615 $line = rc_wordwrap(rtrim($line), $length - 1, " \r\n"); 616 617 $out .= $line . "\r\n"; 618 } 512 619 513 return $out;514 }620 return $out; 621 } 515 622 516 623 } 517
Note: See TracChangeset
for help on using the changeset viewer.
