[geary] Ensure inline parts with a Content Id keep the same id when sending.



commit f48255a7f6284fefffbe2340c9b5384946e827e6
Author: Michael James Gratton <mike vee net>
Date:   Mon Oct 3 17:34:22 2016 +1100

    Ensure inline parts with a Content Id keep the same id when sending.
    
    Since drafts, replies and forwarded HTML messages may reference inline
    images by CID, so ensure that any existing CIDs are retained.
    
    Bug 712995.
    
    * src/client/composer/composer-widget.vala
      (ComposerWidget::get_composed_email): Update ComposedEmail's CID map.
      (ComposerWidget::update_pending_attachments): Only keep any existing
      CID for inline parts.
    
    * src/engine/api/geary-composed-email.vala (ComposedEmail): Keep track of
      CIDs we are interested in and the files they refer to.
      (ComposedEmail::contains_inline_img_src): Added to allow determining if
      a CID has been used in an email. Use class constants, update
      ::replace_inline_img_src to use them.
    
    * src/engine/rfc822/rfc822-message.vala (Message::from_composed_email):
      Include parts with CIDs in the message body, don't include inline
      parts that already have a CID.

 src/client/composer/composer-widget.vala |   18 ++++++---
 src/engine/api/geary-composed-email.vala |   26 +++++++++++---
 src/engine/rfc822/rfc822-message.vala    |   55 ++++++++++++++++++++++++------
 3 files changed, 77 insertions(+), 22 deletions(-)
---
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index 46042a0..5b44425 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -990,6 +990,7 @@ public class ComposerWidget : Gtk.EventBox {
 
         email.attached_files.add_all(this.attached_files);
         email.inline_files.add_all(this.inline_files);
+        email.cid_files.set_all(this.cid_files);
 
         email.img_src_prefix = this.editor_allow_prefix;
 
@@ -1546,14 +1547,19 @@ public class ComposerWidget : Gtk.EventBox {
                     Geary.Mime.DispositionType? type =
                     part.content_disposition.disposition_type;
                     File file = part.file;
-                    if (part.content_id != null) {
-                        this.cid_files[part.content_id] = file;
-                    } else if (type == Geary.Mime.DispositionType.INLINE) {
-                        // Inline part with no CID, so it is not
+                    if (type == Geary.Mime.DispositionType.INLINE) {
+                        // We only care about the Content Ids of
+                        // inline parts, since we need to display them
+                        // in the editor web view. However if an
+                        // inline part does not have a CID, it is not
                         // possible to be referenced from an IMG SRC
-                        // using a cid: URL, hence treat it as an
+                        // using a cid: URL anyway, so treat it as an
                         // attachment instead.
-                        type = Geary.Mime.DispositionType.ATTACHMENT;
+                        if (part.content_id != null) {
+                            this.cid_files[part.content_id] = file;
+                        } else {
+                            type = Geary.Mime.DispositionType.ATTACHMENT;
+                        }
                     }
 
                     if (type == Geary.Mime.DispositionType.INLINE ||
diff --git a/src/engine/api/geary-composed-email.vala b/src/engine/api/geary-composed-email.vala
index d4cb4a0..de49c33 100644
--- a/src/engine/api/geary-composed-email.vala
+++ b/src/engine/api/geary-composed-email.vala
@@ -8,8 +8,11 @@
  * Encapsulates a message created by the user in the composer.
  */
 public class Geary.ComposedEmail : BaseObject {
+
     public const string MAILTO_SCHEME = "mailto:";;
-    
+
+    private const string IMG_SRC_TEMPLATE = "src=\"%s\"";
+
     public const Geary.Email.Field REQUIRED_REPLY_FIELDS =
         Geary.Email.Field.HEADER
         | Geary.Email.Field.BODY
@@ -39,6 +42,7 @@ public class Geary.ComposedEmail : BaseObject {
         default = new Gee.HashSet<File>(Geary.Files.nullable_hash, Geary.Files.nullable_equal); }
     public Gee.Set<File> inline_files { get; private set;
         default = new Gee.HashSet<File>(Geary.Files.nullable_hash, Geary.Files.nullable_equal); }
+    public Gee.Map<string,File> cid_files = new Gee.HashMap<string,File>();
 
     public string img_src_prefix { get; set; default = ""; }
 
@@ -61,7 +65,18 @@ public class Geary.ComposedEmail : BaseObject {
     }
 
     /**
-     * Replaces the IMG SRC value in the HTML part of any matching strings.
+     * Determines if an IMG SRC value is present in the HTML part.
+     *
+     * Returns true if `value` is present as an IMG SRC value.
+     */
+    public bool contains_inline_img_src(string value) {
+        // XXX This and replace_inline_img_src are pretty
+        // hacky. Should probably be working with a DOM tree.
+        return this.body_html.contains(IMG_SRC_TEMPLATE.printf(value));
+    }
+
+    /**
+     * Replaces matching IMG SRC values in the HTML part.
      *
      * Will also remove the random prefix set by the composer for
      * security reasons.
@@ -69,13 +84,14 @@ public class Geary.ComposedEmail : BaseObject {
      * Returns true if `orig` has been replaced by `replacement`.
      */
     public bool replace_inline_img_src(string orig, string replacement) {
-        const string src = "src=\"%s%s\"";
+        // XXX This and contains_inline_img_src are pretty
+        // hacky. Should probably be working with a DOM tree.
         bool ret = false;
         if (this.body_html != null) {
             string old_body = this.body_html;
             this.body_html = old_body.replace(
-                src.printf(this.img_src_prefix, orig),
-                src.printf("", replacement)
+                IMG_SRC_TEMPLATE.printf(this.img_src_prefix + orig),
+                IMG_SRC_TEMPLATE.printf(replacement)
             );
             // Avoid doing a proper comparison so we don't need to scan
             // the whole string again.
diff --git a/src/engine/rfc822/rfc822-message.vala b/src/engine/rfc822/rfc822-message.vala
index de69d85..eb864cc 100644
--- a/src/engine/rfc822/rfc822-message.vala
+++ b/src/engine/rfc822/rfc822-message.vala
@@ -173,22 +173,55 @@ public class Geary.RFC822.Message : BaseObject {
 
         // Body: HTML format (also optional)
         if (email.body_html != null) {
-            // Create parts for inline images, if any, and updating
-            // the IMG SRC attributes as we go.
+            const string CID_URL_PREFIX = "cid:";
             Gee.List<GMime.Object> related_parts =
                 new Gee.LinkedList<GMime.Object>();
-            if (!email.inline_files.is_empty) {
-                uint index = 0;
-                foreach (File file in email.inline_files) {
-                    string cid = "inline_%u@geary".printf(index++);
-                    // Only include the inline file if it is actually
-                    // referenced by the HTML - it may have been
-                    // deleted by the user after being added
+
+            // The files that need to have Content IDs assigned
+            Gee.Set<File> inline_files = new Gee.HashSet<File>(
+                Geary.Files.nullable_hash, Geary.Files.nullable_equal
+            );
+            inline_files.add_all(email.inline_files);
+
+            // Create parts for inline images, if any, and updating
+            // the IMG SRC attributes as we go. An inline file is only
+            // included if it is actually referenced by the HTML - it
+            // may have been deleted by the user after being added.
+
+            // First, treat parts that already have Content Ids
+            // assigned
+            foreach (string cid in email.cid_files.keys) {
+                if (email.contains_inline_img_src(CID_URL_PREFIX + cid)) {
+                    File file = email.cid_files[cid];
+                    GMime.Object? inline_part = get_file_part(
+                        file, Geary.Mime.DispositionType.INLINE
+                    );
+                    if (inline_part != null) {
+                        inline_part.set_content_id(cid);
+                        related_parts.add(inline_part);
+                    }
+                    // Don't need to assign a CID to this file, so
+                    // don't process it below any further.
+                    inline_files.remove(file);
+                }
+            }
+
+            // Then, treat parts that need to have Content Id
+            // assigned.
+            if (!inline_files.is_empty) {
+                const string CID_TEMPLATE = "inline_%02u@geary";
+                uint cid_index = 0;
+                foreach (File file in inline_files) {
+                    string cid = "";
+                    do {
+                        cid = CID_TEMPLATE.printf(cid_index++);
+                    } while (cid in email.cid_files);
+
                     if (email.replace_inline_img_src(file.get_uri(),
-                                                     "cid:" + cid)) {
+                                                     CID_URL_PREFIX + cid)) {
                         GMime.Object? inline_part = get_file_part(
                             file, Geary.Mime.DispositionType.INLINE
-                            );
+                        );
                         if (inline_part != null) {
                             inline_part.set_content_id(cid);
                             related_parts.add(inline_part);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]