From c56b6696e887e9fc3ef99ba9193f812895d5e6b9 Mon Sep 17 00:00:00 2001 From: bohwaz Date: Sun, 28 Jan 2024 16:20:48 +0100 Subject: [PATCH 1/2] Add orphan inline images below the message body * In plaintext mode: all inline images will be displayed below the message body * In HTML mode: all inline images that are **not** referenced in the message HTML body will be listed below the message body This doesn't add more CPU usage as it is using the already called HTML filtering function to discover if an inline part is referenced or not. This fixes issue #5051 and is a bit better than #9150 --- program/actions/mail/index.php | 13 ++++++---- program/actions/mail/show.php | 18 +++++++++----- program/lib/Roundcube/rcube_message.php | 32 +++++++++++++++++++++++++ program/lib/Roundcube/rcube_washtml.php | 15 ++++++++++++ 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/program/actions/mail/index.php b/program/actions/mail/index.php index 0db0403ff05..e7d497b5f7b 100644 --- a/program/actions/mail/index.php +++ b/program/actions/mail/index.php @@ -896,10 +896,11 @@ public static function check_safe($message) * @param string $html HTML * @param array $p Display parameters * @param array $cid_replaces CID map replaces (inline images) + * @param array $used_cids List of CIDs appearing in message body * * @return string Clean HTML */ - public static function wash_html($html, $p, $cid_replaces = []) + public static function wash_html($html, $p, $cid_replaces = [], &$used_cids = []) { $rcmail = rcmail::get_instance(); @@ -975,6 +976,7 @@ public static function wash_html($html, $p, $cid_replaces = []) $html = $washer->wash($html); self::$REMOTE_OBJECTS = $washer->extlinks; + $used_cids = $washer->get_used_cids(); return $html; } @@ -986,10 +988,11 @@ public static function wash_html($html, $p, $cid_replaces = []) * @param string $body Message part body * @param rcube_message_part $part Message part * @param array $p Display parameters array + * @param array $used_cids List of CIDs appearing in message body * * @return string Formatted HTML string */ - public static function print_body($body, $part, $p = []) + public static function print_body($body, $part, $p = [], &$used_cids = []) { $rcmail = rcmail::get_instance(); @@ -1006,6 +1009,8 @@ public static function print_body($body, $part, $p = []) ] ); + $used_cids = []; + // convert html to text/plain if ($data['plain'] && ($data['type'] == 'html' || $data['type'] == 'enriched')) { if ($data['type'] == 'enriched') { @@ -1017,13 +1022,13 @@ public static function print_body($body, $part, $p = []) } // text/html elseif ($data['type'] == 'html') { - $body = self::wash_html($data['body'], $data, $part->replaces); + $body = self::wash_html($data['body'], $data, $part->replaces, $used_cids); $part->ctype_secondary = $data['type']; } // text/enriched elseif ($data['type'] == 'enriched') { $body = rcube_enriched::to_html($data['body']); - $body = self::wash_html($body, $data, $part->replaces); + $body = self::wash_html($body, $data, $part->replaces, $used_cids); $part->ctype_secondary = 'html'; } else { // assert plaintext diff --git a/program/actions/mail/show.php b/program/actions/mail/show.php index ee501737c8f..afe869a8d4c 100644 --- a/program/actions/mail/show.php +++ b/program/actions/mail/show.php @@ -649,6 +649,8 @@ public static function message_body($attrib) } } + $used_cids = []; + if (!empty(self::$MESSAGE->parts)) { foreach (self::$MESSAGE->parts as $part) { if ($part->type == 'headers') { @@ -715,7 +717,7 @@ public static function message_body($attrib) ]; // Parse the part content for display - $body = self::print_body($body, $part, $body_args); + $body = self::print_body($body, $part, $body_args, $used_cids); // check if the message body is PGP encrypted if (strpos($body, '-----BEGIN PGP MESSAGE-----') !== false) { @@ -743,17 +745,21 @@ public static function message_body($attrib) } } - // list images after mail body - if ($rcmail->config->get('inline_images', true) && !empty(self::$MESSAGE->attachments)) { + // Fetch list of orphan inline parts + $orphan_parts = self::$MESSAGE->get_orphan_inline_parts($used_cids); + $attachments = array_merge(self::$MESSAGE->attachments, $orphan_parts); + + // list images after mail body, if it's enabled or if message has orphan parts + if ((!empty($orphan_parts) || $rcmail->config->get('inline_images', true)) && !empty($attachments)) { $thumbnail_size = $rcmail->config->get('image_thumbnail_size', 240); $show_label = rcube::Q($rcmail->gettext('showattachment')); $download_label = rcube::Q($rcmail->gettext('download')); - foreach (self::$MESSAGE->attachments as $attach_prop) { + foreach ($attachments as $attach_prop) { // Content-Type: image/*... if ($mimetype = self::part_image_type($attach_prop)) { - // Skip inline images - if (!self::is_attachment(self::$MESSAGE, $attach_prop)) { + // Skip inline images, unless they are not in the body + if (!self::is_attachment(self::$MESSAGE, $attach_prop) && !in_array($attach_prop, $orphan_parts)) { continue; } diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php index 1edb1ba06d7..ddc8bfc2e17 100644 --- a/program/lib/Roundcube/rcube_message.php +++ b/program/lib/Roundcube/rcube_message.php @@ -1030,6 +1030,38 @@ private function parse_structure($structure, $recursive = false) } } + /** + * Return list of orphan parts: inline images that are not included in the message HTML body + * @param array $used_cids List of used CIDs in body + * @return array + */ + public function get_orphan_inline_parts($used_cids) + { + $orphan_parts = []; + + foreach ($this->inline_parts as $i => $part) { + $id = (string) $part->mime_id; + + // The orphan part will not be returned if it is already in the attachments list + if (array_key_exists($id, $this->attachments)) { + continue; + } + + if (isset($part->content_id)) { + $find = 'cid:' . $part->content_id; + } + elseif (!empty($part->content_location)) { + $find = $part->content_location; + } + + if (!in_array($find, $used_cids)) { + $orphan_parts[$id] = $part; + } + } + + return $orphan_parts; + } + /** * Fill a flat array with references to all parts, indexed by part numbers * diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php index 0531112b3c5..cab198448b5 100644 --- a/program/lib/Roundcube/rcube_washtml.php +++ b/program/lib/Roundcube/rcube_washtml.php @@ -183,6 +183,9 @@ class rcube_washtml /** @var string A prefix to be added to id/class/for attribute values */ private $_css_prefix; + /** @var array List of CIDs used in body */ + private $_used_cids = []; + /** @var int Max nesting level */ private $max_nesting_level; @@ -229,6 +232,16 @@ public function __construct($p = []) } } + /** + * Return list of CIDs that haven't been used in HTML + * + * @return array + */ + public function get_used_cids() + { + return array_keys($this->_used_cids); + } + /** * Register a callback function for a certain tag * @@ -387,11 +400,13 @@ private function wash_attribs($node) private function wash_uri($uri, $blocked_source = false, $is_image = true) { if (!empty($this->config['cid_map'][$uri])) { + $this->_used_cids[$uri] = true; return $this->config['cid_map'][$uri]; } $key = $this->config['base_url'] . $uri; if (!empty($this->config['cid_map'][$key])) { + $this->_used_cids[$key] = true; return $this->config['cid_map'][$key]; } From bb4bf29f6cc179e3cc030dd5d5bdb4a84787d4b0 Mon Sep 17 00:00:00 2001 From: bohwaz Date: Sun, 28 Jan 2024 16:37:26 +0100 Subject: [PATCH 2/2] Make Coding style validation happy --- program/actions/mail/index.php | 8 ++++---- program/lib/Roundcube/rcube_message.php | 9 ++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/program/actions/mail/index.php b/program/actions/mail/index.php index e7d497b5f7b..faf0e50ebb6 100644 --- a/program/actions/mail/index.php +++ b/program/actions/mail/index.php @@ -985,10 +985,10 @@ public static function wash_html($html, $p, $cid_replaces = [], &$used_cids = [] * Convert the given message part to proper HTML * which can be displayed the message view * - * @param string $body Message part body - * @param rcube_message_part $part Message part - * @param array $p Display parameters array - * @param array $used_cids List of CIDs appearing in message body + * @param string $body Message part body + * @param rcube_message_part $part Message part + * @param array $p Display parameters array + * @param array $used_cids List of CIDs appearing in message body * * @return string Formatted HTML string */ diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php index ddc8bfc2e17..647e1974351 100644 --- a/program/lib/Roundcube/rcube_message.php +++ b/program/lib/Roundcube/rcube_message.php @@ -1032,7 +1032,9 @@ private function parse_structure($structure, $recursive = false) /** * Return list of orphan parts: inline images that are not included in the message HTML body - * @param array $used_cids List of used CIDs in body + * + * @param array $used_cids List of used CIDs in body + * * @return array */ public function get_orphan_inline_parts($used_cids) @@ -1049,9 +1051,10 @@ public function get_orphan_inline_parts($used_cids) if (isset($part->content_id)) { $find = 'cid:' . $part->content_id; - } - elseif (!empty($part->content_location)) { + } elseif (!empty($part->content_location)) { $find = $part->content_location; + } else { + continue; } if (!in_array($find, $used_cids)) {