+ // message contains multiple parts
+ 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 = $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;
+ }
+ // part text/[plain|html] or delivery status
+ 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') {
+ $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;
+ }
+ // part message/*
+ 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;
+ }
+ // ignore "virtual" protocol parts
+ else if ($primary_type == 'protocol') {
+ continue;
+ }
+ // part is Microsoft Outlook TNEF (winmail.dat)
+ 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;
+ }
+ }
+ // part is a file/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 ($mimetype == 'multipart/related'
+ && ($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;
+ }
+ // attachment encapsulated within message/rfc822 part needs further decoding (#1486743)
+ 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;
+ }
+ // regular attachment with valid content type
+ // (content-type name regexp according to RFC4288.4.2)
+ else if (preg_match('/^[a-z0-9!#$&.+^_-]+\/[a-z0-9!#$&.+^_-]+$/i', $part_mimetype)) {
+ if (!$mail_part->filename)
+ $mail_part->filename = 'Part '.$mail_part->mime_id;
+
+ $this->attachments[] = $mail_part;
+ }
+ // attachment with invalid content type
+ // replace malformed content type with application/octet-stream (#1487767)
+ 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;
+ }
+ }
+ }
+
+ // if this was a related part try to resolve references
+ if ($mimetype == 'multipart/related' && sizeof($this->inline_parts)) {
+ $a_replaces = array();
+
+ foreach ($this->inline_parts as $inline_object) {
+ $part_url = $this->get_part_url($inline_object->mime_id);
+ if ($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;
+ }
+ // 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($got_html_part) && empty($inline_object->content_id)
+ && !empty($inline_object->filename)
+ ) {
+ $this->attachments[] = $inline_object;
+ }
+ // MS Outlook sometimes also adds non-image attachments as related
+ // We'll add all such attachments to the attachments list
+ // Warning: some browsers support pdf in <img/>
+ // @TODO: we should fetch HTML body and find attachment's content-id
+ // to handle also image attachments without reference in the body
+ if (!empty($inline_object->filename)
+ && !preg_match('/^image\/(gif|jpe?g|png|tiff|bmp|svg)/', $inline_object->mimetype)
+ ) {
+ $this->attachments[] = $inline_object;
+ }
+ }
+
+ // 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;
+ }
+ }