/** * Read the message structure returend by the IMAP server * and build flat lists of content parts and attachments * * @param rcube_message_part $structure Message structure node * @param bool $recursive True when called recursively */ private function parse_structure($structure, $recursive = false) { // real content-type of message/rfc822 part if ($structure->mimetype == 'message/rfc822' && $structure->real_mimetype) { $mimetype = $structure->real_mimetype; // parse headers from message/rfc822 part if (!isset($structure->headers['subject']) && !isset($structure->headers['from'])) { list($headers, ) = explode("\r\n\r\n", $this->get_part_body($structure->mime_id, false, 32768)); $structure->headers = rcube_mime::parse_headers($headers); if ($this->context == $structure->mime_id) { $this->headers = rcube_message_header::from_array($structure->headers); } } } else { $mimetype = $structure->mimetype; } // show message headers if ($recursive && is_array($structure->headers) && (isset($structure->headers['subject']) || $structure->headers['from'] || $structure->headers['to'])) { $c = new stdClass(); $c->type = 'headers'; $c->headers = $structure->headers; $this->add_part($c); } // Allow plugins to handle message parts $plugin = $this->app->plugins->exec_hook('message_part_structure', array('object' => $this, 'structure' => $structure, 'mimetype' => $mimetype, 'recursive' => $recursive)); if ($plugin['abort']) { return; } $structure = $plugin['structure']; $mimetype = $plugin['mimetype']; $recursive = $plugin['recursive']; list($message_ctype_primary, $message_ctype_secondary) = explode('/', $mimetype); // print body if message doesn't have multiple parts if ($message_ctype_primary == 'text' && !$recursive) { // parts with unsupported type add to attachments list if (!in_array($message_ctype_secondary, array('plain', 'html', 'enriched'))) { $this->add_part($structure, 'attachment'); return; } $structure->type = 'content'; $this->add_part($structure); // Parse simple (plain text) message body if ($message_ctype_secondary == 'plain') { foreach ((array) $this->uu_decode($structure) as $uupart) { $this->mime_parts[$uupart->mime_id] = $uupart; $this->add_part($uupart, 'attachment'); } } } else { if ($mimetype == 'application/pgp' && !$recursive) { $structure->type = 'content'; $this->add_part($structure); } else { if ($mimetype == 'multipart/alternative' && is_array($structure->parts) && count($structure->parts) > 1) { // get html/plaintext parts, other add to attachments list foreach ($structure->parts as $p => $sub_part) { $sub_mimetype = $sub_part->mimetype; $is_multipart = preg_match('/^multipart\\/(related|relative|mixed|alternative)/', $sub_mimetype); // skip empty text parts if (!$sub_part->size && !$is_multipart) { continue; } // We've encountered (malformed) messages with more than // one text/plain or text/html part here. There's no way to choose // which one is better, so we'll display first of them and add // others as attachments (#1489358) // check if sub part is if ($is_multipart) { $related_part = $p; } else { if ($sub_mimetype == 'text/plain' && !$plain_part) { $plain_part = $p; } else { if ($sub_mimetype == 'text/html' && !$html_part) { $html_part = $p; $this->got_html_part = true; } else { if ($sub_mimetype == 'text/enriched' && !$enriched_part) { $enriched_part = $p; } else { // add unsupported/unrecognized parts to attachments list $this->add_part($sub_part, 'attachment'); } } } } } // parse related part (alternative part could be in here) if ($related_part !== null && !$this->parse_alternative) { $this->parse_alternative = true; $this->parse_structure($structure->parts[$related_part], true); $this->parse_alternative = false; // if plain part was found, we should unset it if html is preferred if ($this->opt['prefer_html'] && count($this->parts)) { $plain_part = null; } } // choose html/plain part to print if ($html_part !== null && $this->opt['prefer_html']) { $print_part = $structure->parts[$html_part]; } else { if ($enriched_part !== null) { $print_part = $structure->parts[$enriched_part]; } else { if ($plain_part !== null) { $print_part = $structure->parts[$plain_part]; } } } // add the right message body if (is_object($print_part)) { $print_part->type = 'content'; $this->add_part($print_part); } else { if ($html_part !== null && empty($this->parts)) { $c = new stdClass(); $c->type = 'content'; $c->ctype_primary = 'text'; $c->ctype_secondary = 'plain'; $c->mimetype = 'text/plain'; $c->realtype = 'text/html'; $this->add_part($c); } } } else { if ($mimetype == 'multipart/encrypted') { $p = new stdClass(); $p->type = 'content'; $p->ctype_primary = 'text'; $p->ctype_secondary = 'plain'; $p->mimetype = 'text/plain'; $p->realtype = 'multipart/encrypted'; $p->mime_id = $structure->mime_id; $this->add_part($p); // add encrypted payload part as attachment if (is_array($structure->parts)) { for ($i = 0; $i < count($structure->parts); $i++) { $subpart = $structure->parts[$i]; if ($subpart->mimetype == 'application/octet-stream' || !empty($subpart->filename)) { $this->add_part($subpart, 'attachment'); } } } } else { if ($mimetype == 'application/pkcs7-mime') { $p = new stdClass(); $p->type = 'content'; $p->ctype_primary = 'text'; $p->ctype_secondary = 'plain'; $p->mimetype = 'text/plain'; $p->realtype = 'application/pkcs7-mime'; $p->mime_id = $structure->mime_id; $this->add_part($p); if (!empty($structure->filename)) { $this->add_part($structure, 'attachment'); } } else { if (is_array($structure->parts) && !empty($structure->parts)) { // iterate over parts for ($i = 0; $i < count($structure->parts); $i++) { $mail_part =& $structure->parts[$i]; $primary_type = $mail_part->ctype_primary; $secondary_type = $mail_part->ctype_secondary; $part_mimetype = $mail_part->mimetype; // multipart/alternative or message/rfc822 if ($primary_type == 'multipart' || $part_mimetype == 'message/rfc822') { $this->parse_structure($mail_part, true); // list message/rfc822 as attachment as well (mostly .eml) if ($primary_type == 'message' && !empty($mail_part->filename)) { $this->add_part($mail_part, 'attachment'); } } else { if (($part_mimetype == 'text/plain' || $part_mimetype == 'text/html') && $mail_part->disposition != 'attachment' || in_array($part_mimetype, array('message/delivery-status', 'text/rfc822-headers', 'message/disposition-notification'))) { // Allow plugins to handle also this part $plugin = $this->app->plugins->exec_hook('message_part_structure', array('object' => $this, 'structure' => $mail_part, 'mimetype' => $part_mimetype, 'recursive' => true)); if ($plugin['abort']) { continue; } if ($part_mimetype == 'text/html' && $mail_part->size) { $this->got_html_part = true; } $mail_part = $plugin['structure']; list($primary_type, $secondary_type) = explode('/', $plugin['mimetype']); // add text part if it matches the prefs if (!$this->parse_alternative || $secondary_type == 'html' && $this->opt['prefer_html'] || $secondary_type == 'plain' && !$this->opt['prefer_html']) { $mail_part->type = 'content'; $this->add_part($mail_part); } // list as attachment as well if (!empty($mail_part->filename)) { $this->add_part($mail_part, 'attachment'); } } else { if ($primary_type == 'protocol') { continue; } else { if ($part_mimetype == 'application/ms-tnef') { $tnef_parts = (array) $this->tnef_decode($mail_part); foreach ($tnef_parts as $tpart) { $this->mime_parts[$tpart->mime_id] = $tpart; $this->add_part($tpart, 'attachment'); } // add winmail.dat to the list if it's content is unknown if (empty($tnef_parts) && !empty($mail_part->filename)) { $this->mime_parts[$mail_part->mime_id] = $mail_part; $this->add_part($mail_part, 'attachment'); } } else { if (preg_match('/^(inline|attach)/', $mail_part->disposition) || $mail_part->headers['content-id'] || $mail_part->filename && (empty($mail_part->disposition) || preg_match('/^[a-z0-9!#$&.+^_-]+$/i', $mail_part->disposition))) { // skip apple resource forks if ($message_ctype_secondary == 'appledouble' && $secondary_type == 'applefile') { continue; } // part belongs to a related message and is linked if (preg_match('/^multipart\\/(related|relative)/', $mimetype) && ($mail_part->headers['content-id'] || $mail_part->headers['content-location'])) { if ($mail_part->headers['content-id']) { $mail_part->content_id = preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']); } if ($mail_part->headers['content-location']) { $mail_part->content_location = $mail_part->headers['content-base'] . $mail_part->headers['content-location']; } $this->add_part($mail_part, 'inline'); } else { if (preg_match('/^[a-z0-9!#$&.+^_-]+\\/[a-z0-9!#$&.+^_-]+$/i', $part_mimetype)) { $this->add_part($mail_part, 'attachment'); } else { if ($mail_part->filename) { $mail_part->ctype_primary = 'application'; $mail_part->ctype_secondary = 'octet-stream'; $mail_part->mimetype = 'application/octet-stream'; $this->add_part($mail_part, 'attachment'); } } } } else { if ($part_mimetype == 'text/calendar') { if (!$mail_part->filename) { $mail_part->filename = 'calendar.ics'; } $this->add_part($mail_part, 'attachment'); } } } } } } } // if this was a related part try to resolve references if (preg_match('/^multipart\\/(related|relative)/', $mimetype) && sizeof($this->inline_parts)) { $a_replaces = array(); $img_regexp = '/^image\\/(gif|jpe?g|png|tiff|bmp|svg)/'; foreach ($this->inline_parts as $inline_object) { $part_url = $this->get_part_url($inline_object->mime_id, $inline_object->ctype_primary); if (isset($inline_object->content_id)) { $a_replaces['cid:' . $inline_object->content_id] = $part_url; } if ($inline_object->content_location) { $a_replaces[$inline_object->content_location] = $part_url; } if (!empty($inline_object->filename)) { // MS Outlook sends sometimes non-related attachments as related // In this case multipart/related message has only one text part // We'll add all such attachments to the attachments list if (!isset($this->got_html_part)) { $this->add_part($inline_object, 'attachment'); } else { if (!preg_match($img_regexp, $inline_object->mimetype)) { $this->add_part($inline_object, 'attachment'); } } // @TODO: we should fetch HTML body and find attachment's content-id // to handle also image attachments without reference in the body // @TODO: should we list all image attachments in text mode? } } // add replace array to each content part // (will be applied later when part body is available) foreach ($this->parts as $i => $part) { if ($part->type == 'content') { $this->parts[$i]->replaces = $a_replaces; } } } } else { if ($structure->filename || preg_match('/^application\\//i', $mimetype)) { $this->add_part($structure, 'attachment'); } } } } } } } }
/** * Factory method to instantiate headers from a data array * * @param array Hash array with header values * @return object rcube_message_header instance filled with headers values */ public static function from_array($arr) { $obj = new rcube_message_header(); foreach ($arr as $k => $v) { $obj->set($k, $v); } return $obj; }