Пример #1
0
 /**
  * Build message part object
  *
  * @param array  $part
  * @param int    $count
  * @param string $parent
  */
 protected function structure_part($part, $count = 0, $parent = '', $mime_headers = null)
 {
     $struct = new rcube_message_part();
     $struct->mime_id = empty($parent) ? (string) $count : "{$parent}.{$count}";
     // multipart
     if (is_array($part[0])) {
         $struct->ctype_primary = 'multipart';
         /* RFC3501: BODYSTRUCTURE fields of multipart part
                part1 array
                part2 array
                part3 array
                ....
                1. subtype
                2. parameters (optional)
                3. description (optional)
                4. language (optional)
                5. location (optional)
            */
         // find first non-array entry
         for ($i = 1; $i < count($part); $i++) {
             if (!is_array($part[$i])) {
                 $struct->ctype_secondary = strtolower($part[$i]);
                 // read content type parameters
                 if (is_array($part[$i + 1])) {
                     $struct->ctype_parameters = array();
                     for ($j = 0; $j < count($part[$i + 1]); $j += 2) {
                         $param = strtolower($part[$i + 1][$j]);
                         $struct->ctype_parameters[$param] = $part[$i + 1][$j + 1];
                     }
                 }
                 break;
             }
         }
         $struct->mimetype = 'multipart/' . $struct->ctype_secondary;
         // build parts list for headers pre-fetching
         for ($i = 0; $i < count($part); $i++) {
             if (!is_array($part[$i])) {
                 break;
             }
             // fetch message headers if message/rfc822
             // or named part (could contain Content-Location header)
             if (!is_array($part[$i][0])) {
                 $tmp_part_id = $struct->mime_id ? $struct->mime_id . '.' . ($i + 1) : $i + 1;
                 if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') {
                     $mime_part_headers[] = $tmp_part_id;
                 } else {
                     if (in_array('name', (array) $part[$i][2]) && empty($part[$i][3])) {
                         $mime_part_headers[] = $tmp_part_id;
                     }
                 }
             }
         }
         // pre-fetch headers of all parts (in one command for better performance)
         // @TODO: we could do this before _structure_part() call, to fetch
         // headers for parts on all levels
         if ($mime_part_headers) {
             $mime_part_headers = $this->conn->fetchMIMEHeaders($this->folder, $this->msg_uid, $mime_part_headers);
         }
         $struct->parts = array();
         for ($i = 0, $count = 0; $i < count($part); $i++) {
             if (!is_array($part[$i])) {
                 break;
             }
             $tmp_part_id = $struct->mime_id ? $struct->mime_id . '.' . ($i + 1) : $i + 1;
             $struct->parts[] = $this->structure_part($part[$i], ++$count, $struct->mime_id, $mime_part_headers[$tmp_part_id]);
         }
         return $struct;
     }
     /* RFC3501: BODYSTRUCTURE fields of non-multipart part
            0. type
            1. subtype
            2. parameters
            3. id
            4. description
            5. encoding
            6. size
          -- text
            7. lines
          -- message/rfc822
            7. envelope structure
            8. body structure
            9. lines
          --
            x. md5 (optional)
            x. disposition (optional)
            x. language (optional)
            x. location (optional)
        */
     // regular part
     $struct->ctype_primary = strtolower($part[0]);
     $struct->ctype_secondary = strtolower($part[1]);
     $struct->mimetype = $struct->ctype_primary . '/' . $struct->ctype_secondary;
     // read content type parameters
     if (is_array($part[2])) {
         $struct->ctype_parameters = array();
         for ($i = 0; $i < count($part[2]); $i += 2) {
             $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i + 1];
         }
         if (isset($struct->ctype_parameters['charset'])) {
             $struct->charset = $struct->ctype_parameters['charset'];
         }
     }
     // #1487700: workaround for lack of charset in malformed structure
     if (empty($struct->charset) && !empty($mime_headers) && $mime_headers->charset) {
         $struct->charset = $mime_headers->charset;
     }
     // read content encoding
     if (!empty($part[5])) {
         $struct->encoding = strtolower($part[5]);
         $struct->headers['content-transfer-encoding'] = $struct->encoding;
     }
     // get part size
     if (!empty($part[6])) {
         $struct->size = intval($part[6]);
     }
     // read part disposition
     $di = 8;
     if ($struct->ctype_primary == 'text') {
         $di += 1;
     } else {
         if ($struct->mimetype == 'message/rfc822') {
             $di += 3;
         }
     }
     if (is_array($part[$di]) && count($part[$di]) == 2) {
         $struct->disposition = strtolower($part[$di][0]);
         if (is_array($part[$di][1])) {
             for ($n = 0; $n < count($part[$di][1]); $n += 2) {
                 $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n + 1];
             }
         }
     }
     // get message/rfc822's child-parts
     if (is_array($part[8]) && $di != 8) {
         $struct->parts = array();
         for ($i = 0, $count = 0; $i < count($part[8]); $i++) {
             if (!is_array($part[8][$i])) {
                 break;
             }
             $struct->parts[] = $this->structure_part($part[8][$i], ++$count, $struct->mime_id);
         }
     }
     // get part ID
     if (!empty($part[3])) {
         $struct->content_id = $part[3];
         $struct->headers['content-id'] = $part[3];
         if (empty($struct->disposition)) {
             $struct->disposition = 'inline';
         }
     }
     // fetch message headers if message/rfc822 or named part (could contain Content-Location header)
     if ($struct->ctype_primary == 'message' || $struct->ctype_parameters['name'] && !$struct->content_id) {
         if (empty($mime_headers)) {
             $mime_headers = $this->conn->fetchPartHeader($this->folder, $this->msg_uid, true, $struct->mime_id);
         }
         if (is_string($mime_headers)) {
             $struct->headers = rcube_mime::parse_headers($mime_headers) + $struct->headers;
         } else {
             if (is_object($mime_headers)) {
                 $struct->headers = get_object_vars($mime_headers) + $struct->headers;
             }
         }
         // get real content-type of message/rfc822
         if ($struct->mimetype == 'message/rfc822') {
             // single-part
             if (!is_array($part[8][0])) {
                 $struct->real_mimetype = strtolower($part[8][0] . '/' . $part[8][1]);
             } else {
                 for ($n = 0; $n < count($part[8]); $n++) {
                     if (!is_array($part[8][$n])) {
                         break;
                     }
                 }
                 $struct->real_mimetype = 'multipart/' . strtolower($part[8][$n]);
             }
         }
         if ($struct->ctype_primary == 'message' && empty($struct->parts)) {
             if (is_array($part[8]) && $di != 8) {
                 $struct->parts[] = $this->structure_part($part[8], ++$count, $struct->mime_id);
             }
         }
     }
     // normalize filename property
     $this->set_part_filename($struct, $mime_headers);
     return $struct;
 }
Пример #2
0
 /**
  * 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);
         }
     } 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->parts[] = $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'];
     list($message_ctype_primary, $message_ctype_secondary) = explode('/', $plugin['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->attachments[] = $structure;
             return;
         }
         $structure->type = 'content';
         $this->parts[] = $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->attachments[] = $uupart;
             }
         }
     } else {
         if ($mimetype == 'application/pgp' && !$recursive) {
             $structure->type = 'content';
             $this->parts[] = $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->attachments[] = $sub_part;
                                 }
                             }
                         }
                     }
                 }
                 // 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->parts[] = $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->parts[] = $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->parts[] = $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->attachments[] = $subpart;
                             }
                         }
                     }
                 } 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->parts[] = $p;
                         if (!empty($structure->filename)) {
                             $this->attachments[] = $structure;
                         }
                     } 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;
                                 // real content-type of message/rfc822
                                 if ($mail_part->real_mimetype) {
                                     $part_orig_mimetype = $mail_part->mimetype;
                                     $part_mimetype = $mail_part->real_mimetype;
                                     list($primary_type, $secondary_type) = explode('/', $part_mimetype);
                                 } else {
                                     $part_mimetype = $part_orig_mimetype = $mail_part->mimetype;
                                 }
                                 // multipart/alternative
                                 if ($primary_type == 'multipart') {
                                     $this->parse_structure($mail_part, true);
                                     // list message/rfc822 as attachment as well (mostly .eml)
                                     if ($part_orig_mimetype == 'message/rfc822' && !empty($mail_part->filename)) {
                                         $this->attachments[] = $mail_part;
                                     }
                                 } 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->parts[] = $mail_part;
                                         }
                                         // list as attachment as well
                                         if (!empty($mail_part->filename)) {
                                             $this->attachments[] = $mail_part;
                                         }
                                     } else {
                                         if ($primary_type == 'message') {
                                             $this->parse_structure($mail_part, true);
                                             // list as attachment as well (mostly .eml)
                                             if (!empty($mail_part->filename)) {
                                                 $this->attachments[] = $mail_part;
                                             }
                                         } else {
                                             if ($primary_type == 'protocol') {
                                                 continue;
                                             } else {
                                                 if ($part_mimetype == 'application/ms-tnef') {
                                                     foreach ((array) $this->tnef_decode($mail_part) as $tpart) {
                                                         $this->mime_parts[$tpart->mime_id] = $tpart;
                                                         $this->attachments[] = $tpart;
                                                     }
                                                 } 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->inline_parts[] = $mail_part;
                                                         } else {
                                                             if ($part_orig_mimetype == 'message/rfc822') {
                                                                 $this->parse_structure($mail_part, true);
                                                                 // list as attachment as well (mostly .eml)
                                                                 if (!empty($mail_part->filename)) {
                                                                     $this->attachments[] = $mail_part;
                                                                 }
                                                             } else {
                                                                 if (preg_match('/^[a-z0-9!#$&.+^_-]+\\/[a-z0-9!#$&.+^_-]+$/i', $part_mimetype)) {
                                                                     $this->attachments[] = $mail_part;
                                                                 } else {
                                                                     if ($mail_part->filename) {
                                                                         $mail_part->ctype_primary = 'application';
                                                                         $mail_part->ctype_secondary = 'octet-stream';
                                                                         $mail_part->mimetype = 'application/octet-stream';
                                                                         $this->attachments[] = $mail_part;
                                                                     }
                                                                 }
                                                             }
                                                         }
                                                     } else {
                                                         if ($mail_part->mimetype == 'message/rfc822') {
                                                             $this->parse_structure($mail_part);
                                                         } else {
                                                             if ($part_mimetype == 'text/calendar') {
                                                                 if (!$mail_part->filename) {
                                                                     $mail_part->filename = 'calendar.ics';
                                                                 }
                                                                 $this->attachments[] = $mail_part;
                                                             }
                                                         }
                                                     }
                                                 }
                                             }
                                         }
                                     }
                                 }
                             }
                             // 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->attachments[] = $inline_object;
                                         } else {
                                             if (!preg_match($img_regexp, $inline_object->mimetype)) {
                                                 $this->attachments[] = $inline_object;
                                             }
                                         }
                                         // @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) {
                                 $this->attachments[] = $structure;
                             } else {
                                 if (preg_match('/application\\//i', $mimetype)) {
                                     $this->attachments[] = $structure;
                                 }
                             }
                         }
                     }
                 }
             }
         }
     }
 }