From 3412e50b54e3daac8745234e21ab6e72be0ed165 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Wed, 04 Jun 2014 11:20:33 -0400
Subject: [PATCH] Fix attachment menu structure and aria-attributes
---
program/lib/Roundcube/rcube_message.php | 228 ++++++++++++++++++++++++++++++++++++--------------------
1 files changed, 146 insertions(+), 82 deletions(-)
diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index c626af0..a648ae7 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_message.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2010, The Roundcube Dev Team |
| |
@@ -18,7 +16,6 @@
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
-
/**
* Logical representation of a mail message with all its data
@@ -77,6 +74,11 @@
*/
function __construct($uid, $folder = null)
{
+ // decode combined UID-folder identifier
+ if (preg_match('/^\d+-.+/', $uid)) {
+ list($uid, $folder) = explode('-', $uid, 2);
+ }
+
$this->uid = $uid;
$this->app = rcube::get_instance();
$this->storage = $this->app->get_storage();
@@ -88,15 +90,16 @@
$this->headers = $this->storage->get_message($uid);
- if (!$this->headers)
+ if (!$this->headers) {
return;
+ }
$this->mime = new rcube_mime($this->headers->charset);
- $this->subject = $this->mime->decode_mime_string($this->headers->subject);
+ $this->subject = $this->headers->get('subject');
list(, $this->sender) = each($this->mime->decode_address_list($this->headers->from, 1));
- $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$uid]));
+ $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$this->folder.':'.$uid]));
$this->opt = array(
'safe' => $this->is_safe,
'prefer_html' => $this->app->config->get('prefer_html'),
@@ -128,15 +131,11 @@
*/
public function get_header($name, $raw = false)
{
- if (empty($this->headers))
+ if (empty($this->headers)) {
return null;
+ }
- if ($this->headers->$name)
- $value = $this->headers->$name;
- else if ($this->headers->others[$name])
- $value = $this->headers->others[$name];
-
- return $raw ? $value : $this->mime->decode_header($value);
+ return $this->headers->get($name, !$raw);
}
@@ -147,8 +146,7 @@
*/
public function set_safe($safe = true)
{
- $this->is_safe = $safe;
- $_SESSION['safe_messages'][$this->uid] = $this->is_safe;
+ $_SESSION['safe_messages'][$this->folder.':'.$this->uid] = $this->is_safe = $safe;
}
@@ -156,12 +154,13 @@
* Compose a valid URL for getting a message part
*
* @param string $mime_id Part MIME-ID
+ * @param mixed $embed Mimetype class for parts to be embedded
* @return string URL or false if part does not exist
*/
public function get_part_url($mime_id, $embed = false)
{
if ($this->mime_parts[$mime_id])
- return $this->opt['get_url'] . '&_part=' . $mime_id . ($embed ? '&_embed=1' : '');
+ return $this->opt['get_url'] . '&_part=' . $mime_id . ($embed ? '&_embed=1&_mimeclass=' . $embed : '');
else
return false;
}
@@ -174,10 +173,11 @@
* @param resource $fp File pointer to save the message part
* @param boolean $skip_charset_conv Disables charset conversion
* @param int $max_bytes Only read this number of bytes
+ * @param boolean $formatted Enables formatting of text/* parts bodies
*
* @return string Part content
*/
- public function get_part_content($mime_id, $fp = null, $skip_charset_conv = false, $max_bytes = 0)
+ public function get_part_content($mime_id, $fp = null, $skip_charset_conv = false, $max_bytes = 0, $formatted = true)
{
if ($part = $this->mime_parts[$mime_id]) {
// stored in message structure (winmail/inline-uuencode)
@@ -191,45 +191,91 @@
// get from IMAP
$this->storage->set_folder($this->folder);
- return $this->storage->get_message_part($this->uid, $mime_id, $part, NULL, $fp, $skip_charset_conv, $max_bytes);
+ return $this->storage->get_message_part($this->uid, $mime_id, $part,
+ NULL, $fp, $skip_charset_conv, $max_bytes, $formatted);
}
}
/**
- * Determine if the message contains a HTML part
+ * Determine if the message contains a HTML part. This must to be
+ * a real part not an attachment (or its part)
*
- * @param bool $recursive Enables checking in all levels of the structure
- * @param bool $enriched Enables checking for text/enriched parts too
+ * @param bool $enriched Enables checking for text/enriched parts too
*
* @return bool True if a HTML is available, False if not
*/
- function has_html_part($recursive = true, $enriched = false)
+ function has_html_part($enriched = false)
{
// check all message parts
- foreach ($this->parts as $part) {
+ foreach ($this->mime_parts as $part) {
if ($part->mimetype == 'text/html' || ($enriched && $part->mimetype == 'text/enriched')) {
- // Level check, we'll skip e.g. HTML attachments
- if (!$recursive) {
- $level = explode('.', $part->mime_id);
+ // Skip if part is an attachment, don't use is_attachment() here
+ if ($part->filename) {
+ continue;
+ }
- // Skip if level too deep or part has a file name
- if (count($level) > 2 || $part->filename) {
- continue;
+ $level = explode('.', $part->mime_id);
+ $depth = count($level);
+
+ // Check if the part belongs to higher-level's multipart part
+ // this can be alternative/related/signed/encrypted or mixed
+ while (array_pop($level) !== null) {
+ $parent_depth = count($level);
+ if (!$parent_depth) {
+ return true;
}
- // HTML part can be on the lower level, if not...
- if (count($level) > 1) {
- array_pop($level);
- $parent = $this->mime_parts[join('.', $level)];
- // ... parent isn't multipart/alternative or related
- if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') {
- continue;
- }
+ $parent = $this->mime_parts[join('.', $level)];
+ if (!preg_match('/^multipart\/(alternative|related|signed|encrypted|mixed)$/', $parent->mimetype)
+ || ($parent->mimetype == 'multipart/mixed' && $parent_depth < $depth - 1)) {
+ continue 2;
}
}
- return true;
+ if ($part->size) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Determine if the message contains a text/plain part. This must to be
+ * a real part not an attachment (or its part)
+ *
+ * @return bool True if a plain text part is available, False if not
+ */
+ function has_text_part()
+ {
+ // check all message parts
+ foreach ($this->mime_parts as $part) {
+ if ($part->mimetype == 'text/plain') {
+ // Skip if part is an attachment, don't use is_attachment() here
+ if ($part->filename) {
+ continue;
+ }
+
+ $level = explode('.', $part->mime_id);
+
+ // Check if the part belongs to higher-level's alternative/related
+ while (array_pop($level) !== null) {
+ if (!count($level)) {
+ return true;
+ }
+
+ $parent = $this->mime_parts[join('.', $level)];
+ if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') {
+ continue 2;
+ }
+ }
+
+ if ($part->size) {
+ return true;
+ }
}
}
@@ -274,7 +320,7 @@
$out = $this->get_part_content($mime_id);
// create instance of html2text class
- $txt = new html2text($out);
+ $txt = new rcube_html2text($out);
return $txt->get_text();
}
}
@@ -324,8 +370,8 @@
$mimetype = $structure->real_mimetype;
// parse headers from message/rfc822 part
- if (!isset($structure->headers['subject'])) {
- list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 4096));
+ if (!isset($structure->headers['subject']) && !isset($structure->headers['from'])) {
+ list($headers, ) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 32768));
$structure->headers = rcube_mime::parse_headers($headers);
}
}
@@ -333,10 +379,11 @@
$mimetype = $structure->mimetype;
// show message headers
- if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) {
+ 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;
+ $c->headers = $structure->headers;
$this->parts[] = $c;
}
@@ -353,45 +400,66 @@
// 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;
+ $this->parts[] = $structure;
// Parse simple (plain text) message body
- if ($message_ctype_secondary == 'plain')
+ if ($message_ctype_secondary == 'plain') {
foreach ((array)$this->uu_decode($structure) as $uupart) {
$this->mime_parts[$uupart->mime_id] = $uupart;
$this->attachments[] = $uupart;
}
+ }
}
// the same for pgp signed messages
else if ($mimetype == 'application/pgp' && !$recursive) {
$structure->type = 'content';
- $this->parts[] = &$structure;
+ $this->parts[] = $structure;
}
// message contains (more than one!) alternative parts
else if ($mimetype == 'multipart/alternative'
&& is_array($structure->parts) && count($structure->parts) > 1
) {
- // get html/plaintext parts
- $plain_part = $html_part = $print_part = $related_part = null;
+ $plain_part = null;
+ $html_part = null;
+ $print_part = null;
+ $related_part = null;
+ $attach_part = null;
+ // 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 && preg_match('#^text/(plain|html|enriched)$#', $sub_mimetype)) {
+ 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 ($sub_mimetype == 'text/plain')
- $plain_part = $p;
- else if ($sub_mimetype == 'text/html')
- $html_part = $p;
- else if ($sub_mimetype == 'text/enriched')
- $enriched_part = $p;
- else if (in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative')))
+ 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;
+ 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)
@@ -407,13 +475,13 @@
// choose html/plain part to print
if ($html_part !== null && $this->opt['prefer_html']) {
- $print_part = &$structure->parts[$html_part];
+ $print_part = $structure->parts[$html_part];
}
else if ($enriched_part !== null) {
- $print_part = &$structure->parts[$enriched_part];
+ $print_part = $structure->parts[$enriched_part];
}
else if ($plain_part !== null) {
- $print_part = &$structure->parts[$plain_part];
+ $print_part = $structure->parts[$plain_part];
}
// add the right message body
@@ -432,14 +500,6 @@
$this->parts[] = $c;
}
-
- // add html part as attachment
- if ($html_part !== null && $structure->parts[$html_part] !== $print_part) {
- $html_part = &$structure->parts[$html_part];
- $html_part->mimetype = 'text/html';
-
- $this->attachments[] = $html_part;
- }
}
// this is an ecrypted message -> create a plaintext body with the according message
else if ($mimetype == 'multipart/encrypted') {
@@ -449,6 +509,17 @@
$p->ctype_secondary = 'plain';
$p->mimetype = 'text/plain';
$p->realtype = 'multipart/encrypted';
+
+ $this->parts[] = $p;
+ }
+ // this is an S/MIME ecrypted message -> create a plaintext body with the according message
+ 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';
$this->parts[] = $p;
}
@@ -466,8 +537,9 @@
$part_mimetype = $mail_part->real_mimetype;
list($primary_type, $secondary_type) = explode('/', $part_mimetype);
}
- else
- $part_mimetype = $mail_part->mimetype;
+ else {
+ $part_mimetype = $part_orig_mimetype = $mail_part->mimetype;
+ }
// multipart/alternative
if ($primary_type == 'multipart') {
@@ -509,10 +581,6 @@
if (!empty($mail_part->filename)) {
$this->attachments[] = $mail_part;
}
- // list html part as attachment (here the part is most likely inside a multipart/related part)
- else if ($this->parse_alternative && ($secondary_type == 'html' && !$this->opt['prefer_html'])) {
- $this->attachments[] = $mail_part;
- }
}
// part message/*
else if ($primary_type == 'message') {
@@ -544,7 +612,7 @@
continue;
// part belongs to a related message and is linked
- if ($mimetype == 'multipart/related'
+ 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']);
@@ -564,9 +632,6 @@
// 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
@@ -586,13 +651,13 @@
}
// if this was a related part try to resolve references
- if ($mimetype == 'multipart/related' && sizeof($this->inline_parts)) {
+ 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, true);
- if ($inline_object->content_id)
+ $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;
@@ -631,7 +696,6 @@
}
// message is a single part non-text (without filename)
else if (preg_match('/application\//i', $mimetype)) {
- $structure->filename = 'Part '.$structure->mime_id;
$this->attachments[] = $structure;
}
}
@@ -733,7 +797,7 @@
$uupart->size = strlen($uupart->body);
$uupart->mime_id = 'uu.' . $part->mime_id . '.' . $pid;
- $ctype = rcube_mime::content_type($uupart->body, $uupart->filename, 'application/octet-stream', true);
+ $ctype = rcube_mime::file_content_type($uupart->body, $uupart->filename, 'application/octet-stream', true);
$uupart->mimetype = $ctype;
list($uupart->ctype_primary, $uupart->ctype_secondary) = explode('/', $ctype);
--
Gitblit v1.9.1