/**
  * 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;
 }