- Fix forwarding of messages with winmail attachments

- Remove some redundant code for winmail handling in get.inc, move tnef_decode() to rcube_message
- Fix handling of uuencoded attachments in message body (#1485839)
- Extend rc_mime_content_type() to work with string buffer
release-0.6
alecpl 15 years ago
parent 89e31bec15
commit d311d809d6

@ -1,6 +1,8 @@
CHANGELOG RoundCube Webmail CHANGELOG RoundCube Webmail
=========================== ===========================
- Fix forwarding of messages with winmail attachments
- Fix handling of uuencoded attachments in message body (#1485839)
- Added list_mailboxes hook in rcube_imap::list_unsubscribed() (#1486668) - Added list_mailboxes hook in rcube_imap::list_unsubscribed() (#1486668)
- Fix wrong message on file upload error (#1486725) - Fix wrong message on file upload error (#1486725)
- Add support for data URI scheme [RFC2397] (#1486740) - Add support for data URI scheme [RFC2397] (#1486740)

@ -472,7 +472,6 @@ class rcube_imap
// get message count and store in cache // get message count and store in cache
if ($mode == 'UNSEEN') if ($mode == 'UNSEEN')
$search_str .= " UNSEEN"; $search_str .= " UNSEEN";
// get message count using SEARCH // get message count using SEARCH
// not very performant but more precise (using UNDELETED) // not very performant but more precise (using UNDELETED)
// disable THREADS for this request // disable THREADS for this request
@ -3382,43 +3381,6 @@ class rcube_imap
} }
/**
* Decode a Microsoft Outlook TNEF part (winmail.dat)
*
* @param object rcube_message_part Message part to decode
* @param string UID of the message
* @return array List of rcube_message_parts extracted from windmail.dat
*/
function tnef_decode(&$part, $uid)
{
if (!isset($part->body))
$part->body = $this->get_message_part($uid, $part->mime_id, $part);
require_once('lib/tnef_decoder.inc');
$pid = 0;
$tnef_parts = array();
$tnef_arr = tnef_decode($part->body);
foreach ($tnef_arr as $winatt) {
$tpart = new rcube_message_part;
$tpart->filename = trim($winatt['name']);
$tpart->encoding = 'stream';
$tpart->ctype_primary = trim(strtolower($winatt['type0']));
$tpart->ctype_secondary = trim(strtolower($winatt['type1']));
$tpart->mimetype = $tpart->ctype_primary . '/' . $tpart->ctype_secondary;
$tpart->mime_id = "winmail." . $part->mime_id . ".$pid";
$tpart->size = $winatt['size'];
$tpart->body = $winatt['stream'];
$tnef_parts[] = $tpart;
$pid++;
}
return $tnef_parts;
}
/** /**
* Decode a message header value * Decode a message header value
* *

@ -67,14 +67,16 @@ class rcube_message
$this->uid = $uid; $this->uid = $uid;
$this->headers = $this->imap->get_headers($uid, NULL, true, true); $this->headers = $this->imap->get_headers($uid, NULL, true, true);
$this->subject = rcube_imap::decode_mime_string($this->headers->subject, $this->headers->charset); $this->subject = rcube_imap::decode_mime_string(
$this->headers->subject, $this->headers->charset);
list(, $this->sender) = each($this->imap->decode_address_list($this->headers->from)); list(, $this->sender) = each($this->imap->decode_address_list($this->headers->from));
$this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$uid])); $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$uid]));
$this->opt = array( $this->opt = array(
'safe' => $this->is_safe, 'safe' => $this->is_safe,
'prefer_html' => $this->app->config->get('prefer_html'), 'prefer_html' => $this->app->config->get('prefer_html'),
'get_url' => rcmail_url('get', array('_mbox' => $this->imap->get_mailbox_name(), '_uid' => $uid)) 'get_url' => rcmail_url('get', array(
'_mbox' => $this->imap->get_mailbox_name(), '_uid' => $uid))
); );
if ($this->structure = $this->imap->get_structure($uid, $this->headers->body_structure)) { if ($this->structure = $this->imap->get_structure($uid, $this->headers->body_structure)) {
@ -103,6 +105,7 @@ class rcube_message
return $raw ? $value : $this->imap->decode_header($value); return $raw ? $value : $this->imap->decode_header($value);
} }
/** /**
* Set is_safe var and session data * Set is_safe var and session data
* *
@ -114,6 +117,7 @@ class rcube_message
$_SESSION['safe_messages'][$this->uid] = $this->is_safe; $_SESSION['safe_messages'][$this->uid] = $this->is_safe;
} }
/** /**
* Compose a valid URL for getting a message part * Compose a valid URL for getting a message part
* *
@ -123,7 +127,7 @@ class rcube_message
public function get_part_url($mime_id) public function get_part_url($mime_id)
{ {
if ($this->mime_parts[$mime_id]) if ($this->mime_parts[$mime_id])
return $this->opt['get_url'] . "&_part=" . $mime_id; return $this->opt['get_url'] . '&_part=' . $mime_id;
else else
return false; return false;
} }
@ -138,9 +142,17 @@ class rcube_message
*/ */
public function get_part_content($mime_id, $fp=NULL) public function get_part_content($mime_id, $fp=NULL)
{ {
if ($part = $this->mime_parts[$mime_id]) if ($part = $this->mime_parts[$mime_id]) {
// stored in message structure (winmail/inline-uuencode)
if ($part->encoding == 'stream') {
if ($fp) {
fwrite($fp, $part->body);
}
return $fp ? true : $part->body;
}
// get from IMAP
return $this->imap->get_message_part($this->uid, $mime_id, $part, NULL, $fp); return $this->imap->get_message_part($this->uid, $mime_id, $part, NULL, $fp);
else } else
return null; return null;
} }
@ -162,6 +174,7 @@ class rcube_message
return false; return false;
} }
/** /**
* Return the first HTML part of this message * Return the first HTML part of this message
* *
@ -194,13 +207,13 @@ class rcube_message
// check all message parts // check all message parts
foreach ($this->mime_parts as $mime_id => $part) { foreach ($this->mime_parts as $mime_id => $part) {
$mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary); $mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
if ($mimetype == 'text/plain') { if ($mimetype == 'text/plain') {
$out = $this->imap->get_message_part($this->uid, $mime_id, $part); $out = $this->imap->get_message_part($this->uid, $mime_id, $part);
// re-format format=flowed content // re-format format=flowed content
if ($part->ctype_secondary == "plain" && $part->ctype_parameters['format'] == "flowed") if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed')
$out = self::unfold_flowed($out); $out = self::unfold_flowed($out);
break; break;
} }
@ -255,6 +268,15 @@ class rcube_message
if ($message_ctype_primary == 'text' && !$recursive) { if ($message_ctype_primary == 'text' && !$recursive) {
$structure->type = 'content'; $structure->type = 'content';
$this->parts[] = &$structure; $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;
}
// @TODO: plugin hook?
} }
// the same for pgp signed messages // the same for pgp signed messages
else if ($mimetype == 'application/pgp' && !$recursive) { else if ($mimetype == 'application/pgp' && !$recursive) {
@ -311,9 +333,9 @@ class rcube_message
else if ($html_part !== null && empty($this->parts)) { else if ($html_part !== null && empty($this->parts)) {
$c = new stdClass; $c = new stdClass;
$c->type = 'content'; $c->type = 'content';
$c->body = rcube_label('htmlmessage');
$c->ctype_primary = 'text'; $c->ctype_primary = 'text';
$c->ctype_secondary = 'plain'; $c->ctype_secondary = 'plain';
$c->body = rcube_label('htmlmessage');
$this->parts[] = $c; $this->parts[] = $c;
} }
@ -337,7 +359,9 @@ class rcube_message
$p->size = strlen($p->body); $p->size = strlen($p->body);
// maybe some plugins are able to decode this encrypted message part // maybe some plugins are able to decode this encrypted message part
$data = $this->app->plugins->exec_hook('message_part_encrypted', array('object' => $this, 'struct' => $structure, 'part' => $p)); $data = $this->app->plugins->exec_hook('message_part_encrypted',
array('object' => $this, 'struct' => $structure, 'part' => $p));
if (is_array($data['parts'])) { if (is_array($data['parts'])) {
$this->parts = array_merge($this->parts, $data['parts']); $this->parts = array_merge($this->parts, $data['parts']);
} }
@ -372,12 +396,13 @@ class rcube_message
} }
// part text/[plain|html] OR message/delivery-status // part text/[plain|html] OR message/delivery-status
else if ((($part_mimetype == 'text/plain' || $part_mimetype == 'text/html') && $mail_part->disposition != 'attachment') || else if ((($part_mimetype == 'text/plain' || $part_mimetype == 'text/html') && $mail_part->disposition != 'attachment') ||
$part_mimetype == 'message/delivery-status' || $part_mimetype == 'message/disposition-notification') { $part_mimetype == 'message/delivery-status' || $part_mimetype == 'message/disposition-notification'
) {
// add text part if it matches the prefs // add text part if it matches the prefs
if (!$this->parse_alternative || if (!$this->parse_alternative ||
($secondary_type == 'html' && $this->opt['prefer_html']) || ($secondary_type == 'html' && $this->opt['prefer_html']) ||
($secondary_type == 'plain' && !$this->opt['prefer_html'])) { ($secondary_type == 'plain' && !$this->opt['prefer_html'])
) {
$mail_part->type = 'content'; $mail_part->type = 'content';
$this->parts[] = $mail_part; $this->parts[] = $mail_part;
} }
@ -395,20 +420,20 @@ class rcube_message
$this->attachments[] = $mail_part; $this->attachments[] = $mail_part;
} }
// ignore "virtual" protocol parts // ignore "virtual" protocol parts
else if ($primary_type == 'protocol') else if ($primary_type == 'protocol') {
continue; continue;
}
// part is Microsoft Outlook TNEF (winmail.dat) // part is Microsoft Outlook TNEF (winmail.dat)
else if ($part_mimetype == 'application/ms-tnef') { else if ($part_mimetype == 'application/ms-tnef') {
foreach ((array)$this->imap->tnef_decode($mail_part, $structure->headers['uid']) as $tnef_part) { foreach ((array)$this->tnef_decode($mail_part) as $tpart) {
$this->mime_parts[$tnef_part->mime_id] = $tnef_part; $this->mime_parts[$tpart->mime_id] = $tpart;
$this->attachments[] = $tnef_part; $this->attachments[] = $tpart;
} }
} }
// part is a file/attachment // part is a file/attachment
else if (preg_match('/^(inline|attach)/', $mail_part->disposition) || else if (preg_match('/^(inline|attach)/', $mail_part->disposition) ||
$mail_part->headers['content-id'] || (empty($mail_part->disposition) && $mail_part->filename)) { $mail_part->headers['content-id'] || (empty($mail_part->disposition) && $mail_part->filename)
) {
// skip apple resource forks // skip apple resource forks
if ($message_ctype_secondary == 'appledouble' && $secondary_type == 'applefile') if ($message_ctype_secondary == 'appledouble' && $secondary_type == 'applefile')
continue; continue;
@ -457,7 +482,6 @@ class rcube_message
} }
} }
} }
// message is a single part non-text // message is a single part non-text
else if ($structure->filename) { else if ($structure->filename) {
$this->attachments[] = $structure; $this->attachments[] = $structure;
@ -481,6 +505,88 @@ class rcube_message
} }
/**
* Decode a Microsoft Outlook TNEF part (winmail.dat)
*
* @param object rcube_message_part Message part to decode
*/
function tnef_decode(&$part)
{
// @TODO: attachment may be huge, hadle it via file
if (!isset($part->body))
$part->body = $this->imap->get_message_part($this->uid, $part->mime_id, $part);
require_once('lib/tnef_decoder.inc');
$parts = array();
$tnef_arr = tnef_decode($part->body);
foreach ($tnef_arr as $pid => $winatt) {
$tpart = new rcube_message_part;
$tpart->filename = trim($winatt['name']);
$tpart->encoding = 'stream';
$tpart->ctype_primary = trim(strtolower($winatt['type0']));
$tpart->ctype_secondary = trim(strtolower($winatt['type1']));
$tpart->mimetype = $tpart->ctype_primary . '/' . $tpart->ctype_secondary;
$tpart->mime_id = 'winmail.' . $part->mime_id . '.' . $pid;
$tpart->size = $winatt['size'];
$tpart->body = $winatt['stream'];
$parts[] = $tpart;
unset($tnef_arr[$pid]);
}
return $parts;
}
/**
* Parse message body for UUencoded attachments bodies
*
* @param object rcube_message_part Message part to decode
*/
function uu_decode(&$part)
{
// @TODO: messages may be huge, hadle body via file
if (!isset($part->body))
$part->body = $this->imap->get_message_part($this->uid, $part->mime_id, $part);
$parts = array();
// FIXME: line length is max.65?
$uu_regexp = '/begin [0-7]{3,4} ([^\n]+)\n(([\x21-\x7E]{0,65}\n)+)`\nend/s';
if (preg_match_all($uu_regexp, $part->body, $matches, PREG_SET_ORDER)) {
// remove attachments bodies from the message body
$part->body = preg_replace($uu_regexp, '', $part->body);
// update message content-type
$part->ctype_primary = 'multipart';
$part->ctype_secondary = 'mixed';
$part->mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
// add attachments to the structure
foreach ($matches as $pid => $att) {
$uupart = new rcube_message_part;
$uupart->filename = trim($att[1]);
$uupart->encoding = 'stream';
$uupart->body = convert_uudecode($att[2]);
$uupart->size = strlen($uupart->body);
$uupart->mime_id = 'uu.' . $part->mime_id . '.' . $pid;
$ctype = rc_mime_content_type($uupart->body, $uupart->filename, 'application/octet-stream', true);
$uupart->mimetype = $ctype;
list($uupart->ctype_primary, $uupart->ctype_secondary) = explode('/', $ctype);
$parts[] = $uupart;
unset($matches[$pid]);
}
}
return $parts;
}
/** /**
* Interpret a format=flowed message body according to RFC 2646 * Interpret a format=flowed message body according to RFC 2646
* *
@ -495,6 +601,7 @@ class rcube_message
$text); $text);
} }
/** /**
* Wrap the given text to comply with RFC 2646 * Wrap the given text to comply with RFC 2646
*/ */
@ -514,4 +621,3 @@ class rcube_message
} }
} }

@ -433,13 +433,14 @@ function abbreviate_string($str, $maxlength, $place_holder='...')
* @param string $path Path to the file. * @param string $path Path to the file.
* @param string $name File name (with suffix) * @param string $name File name (with suffix)
* @param string $failover Mime type supplied for failover. * @param string $failover Mime type supplied for failover.
* @param string $is_stream Set to True if $path contains file body
* *
* @return string * @return string
* @author Till Klampaeckel <till@php.net> * @author Till Klampaeckel <till@php.net>
* @see http://de2.php.net/manual/en/ref.fileinfo.php * @see http://de2.php.net/manual/en/ref.fileinfo.php
* @see http://de2.php.net/mime_content_type * @see http://de2.php.net/mime_content_type
*/ */
function rc_mime_content_type($path, $name, $failover = 'application/octet-stream') function rc_mime_content_type($path, $name, $failover = 'application/octet-stream', $is_stream=false)
{ {
$mime_type = null; $mime_type = null;
$mime_magic = rcmail::get_instance()->config->get('mime_magic'); $mime_magic = rcmail::get_instance()->config->get('mime_magic');
@ -453,13 +454,16 @@ function rc_mime_content_type($path, $name, $failover = 'application/octet-strea
// try fileinfo extension if available // try fileinfo extension if available
if (!$mime_type && function_exists('finfo_open')) { if (!$mime_type && function_exists('finfo_open')) {
if ($finfo = finfo_open(FILEINFO_MIME, $mime_magic)) { if ($finfo = finfo_open(FILEINFO_MIME, $mime_magic)) {
if ($is_stream)
$mime_type = finfo_buffer($finfo, $path);
else
$mime_type = finfo_file($finfo, $path); $mime_type = finfo_file($finfo, $path);
finfo_close($finfo); finfo_close($finfo);
} }
} }
// try PHP's mime_content_type // try PHP's mime_content_type
if (!$mime_type && function_exists('mime_content_type')) { if (!$mime_type && !$is_stream && function_exists('mime_content_type')) {
$mime_type = mime_content_type($path); $mime_type = @mime_content_type($path);
} }
// fall back to user-submitted string // fall back to user-submitted string
if (!$mime_type) { if (!$mime_type) {

@ -725,8 +725,9 @@ function rcmail_write_compose_attachments(&$message, $bodyIsHtml)
foreach ((array)$message->mime_parts as $pid => $part) foreach ((array)$message->mime_parts as $pid => $part)
{ {
if (($part->ctype_primary != 'message' || !$bodyIsHtml) && $part->ctype_primary != 'multipart' && if (($part->ctype_primary != 'message' || !$bodyIsHtml) && $part->ctype_primary != 'multipart' &&
($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename)) ($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename)
{ && $part->mimetype != 'application/ms-tnef'
) {
$skip = false; $skip = false;
if ($part->mimetype == 'message/rfc822') { if ($part->mimetype == 'message/rfc822') {
$messages[] = $part->mime_id; $messages[] = $part->mime_id;

@ -50,15 +50,6 @@ if (!empty($_GET['_frame'])) {
} }
else if ($pid = get_input_value('_part', RCUBE_INPUT_GET)) { else if ($pid = get_input_value('_part', RCUBE_INPUT_GET)) {
// TNEF encoded attachment part
if (preg_match('/^winmail\.([0-9.]+)\.([0-9]+)$/', $pid, $nt)) {
$pid = $nt[1]; $i = $nt[2];
if ($part = $MESSAGE->mime_parts[$pid]) {
$tnef_arr = $IMAP->tnef_decode($part, $MESSAGE->uid);
if (is_a($tnef_arr[$i], 'rcube_message_part'))
$MESSAGE->mime_parts[$pid] = $tnef_arr[$i];
}
}
if ($part = $MESSAGE->mime_parts[$pid]) { if ($part = $MESSAGE->mime_parts[$pid]) {
$ctype_primary = strtolower($part->ctype_primary); $ctype_primary = strtolower($part->ctype_primary);

Loading…
Cancel
Save