[geary/wip/743676-cid] Candidate fix.



commit 58a713e22ef8d1a7ee332cd18ec1cb57eb8e8e82
Author: Jim Nelson <jim yorba org>
Date:   Thu Feb 12 17:13:59 2015 -0800

    Candidate fix.

 src/CMakeLists.txt                                 |    1 +
 .../conversation-viewer/conversation-viewer.vala   |   17 +++--
 src/engine/mime/mime-content-type.vala             |    4 +-
 src/engine/mime/mime-multipart-subtype.vala        |   73 ++++++++++++++++++++
 src/engine/rfc822/rfc822-message.vala              |   51 +++++++++-----
 5 files changed, 121 insertions(+), 25 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6ee272d..78151fc 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -243,6 +243,7 @@ engine/mime/mime-content-type.vala
 engine/mime/mime-data-format.vala
 engine/mime/mime-disposition-type.vala
 engine/mime/mime-error.vala
+engine/mime/mime-multipart-subtype.vala
 
 engine/nonblocking/nonblocking-abstract-semaphore.vala
 engine/nonblocking/nonblocking-batch.vala
diff --git a/src/client/conversation-viewer/conversation-viewer.vala 
b/src/client/conversation-viewer/conversation-viewer.vala
index d00e5a7..ce7ae0f 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -917,7 +917,13 @@ public class ConversationViewer : Gtk.Box {
     
     private string? inline_image_replacer(string filename, Geary.Mime.ContentType? content_type,
         Geary.Mime.ContentDisposition? disposition, string? content_id, Geary.Memory.Buffer buffer) {
-        if (content_type == null || !is_content_type_supported_inline(content_type)) {
+        if (content_type == null) {
+            debug("Not displaying inline: no Content-Type");
+            
+            return null;
+        }
+        
+        if (!is_content_type_supported_inline(content_type)) {
             debug("Not displaying %s inline: unsupported Content-Type", content_type.to_string());
             
             return null;
@@ -1958,7 +1964,7 @@ public class ConversationViewer : Gtk.Box {
                 if (Geary.String.is_empty(src))
                     continue;
                 
-                // if no Content-ID, then leave as-is, but note if a data: URI is being used for
+                // if no Content-ID, then leave as-is, but note if a non-data: URI is being used for
                 // purposes of detecting remote images
                 string? content_id = src.has_prefix("cid:") ? src.substring(4) : null;
                 if (Geary.String.is_empty(content_id)) {
@@ -1981,10 +1987,10 @@ public class ConversationViewer : Gtk.Box {
                         guess = ContentType.guess(null, unowned_buffer.to_unowned_uint8_array(), null);
                     else
                         guess = ContentType.guess(null, image_content.get_uint8_array(), null);
-                        
+                    
                     string mimetype = ContentType.get_mime_type(guess);
                     
-                    // Replace the SRC to a data URIm the class to a known label for the popup menu,
+                    // Replace the SRC to a data URI, the class to a known label for the popup menu,
                     // and the ALT to its filename, if supplied
                     img.set_attribute("src", assemble_data_uri(mimetype, image_content));
                     img.set_attribute("class", DATA_IMAGE_CLASS);
@@ -1995,6 +2001,7 @@ public class ConversationViewer : Gtk.Box {
                     // Content-Disposition)
                     inlined_content_ids.add(content_id);
                 } else {
+                    // replaced by data: URI, remove this tag and let the inserted one shine through
                     img.parent_element.remove_child(img);
                 }
             }
@@ -2010,7 +2017,7 @@ public class ConversationViewer : Gtk.Box {
                     debug("Error removing inlined image: %s", error.message);
                 }
             }
-
+            
             // Now return the whole message.
             return container.get_inner_html();
         } catch (Error e) {
diff --git a/src/engine/mime/mime-content-type.vala b/src/engine/mime/mime-content-type.vala
index 724535c..c4dc791 100644
--- a/src/engine/mime/mime-content-type.vala
+++ b/src/engine/mime/mime-content-type.vala
@@ -90,7 +90,7 @@ public class Geary.Mime.ContentType : Geary.BaseObject {
      * @see is_type
      */
     public bool has_media_type(string media_type) {
-        return (media_type != WILDCARD) ? String.stri_equal(this.media_type, media_type) : true;
+        return (media_type != WILDCARD) ? Ascii.stri_equal(this.media_type, media_type) : true;
     }
     
     /**
@@ -101,7 +101,7 @@ public class Geary.Mime.ContentType : Geary.BaseObject {
      * @see is_type
      */
     public bool has_media_subtype(string media_subtype) {
-        return (media_subtype != WILDCARD) ? String.stri_equal(this.media_subtype, media_subtype) : true;
+        return (media_subtype != WILDCARD) ? Ascii.stri_equal(this.media_subtype, media_subtype) : true;
     }
     
     /**
diff --git a/src/engine/mime/mime-multipart-subtype.vala b/src/engine/mime/mime-multipart-subtype.vala
new file mode 100644
index 0000000..9f216c9
--- /dev/null
+++ b/src/engine/mime/mime-multipart-subtype.vala
@@ -0,0 +1,73 @@
+/* Copyright 2015 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * A represenation of a MIME multipart Content-Type subtype.
+ *
+ * See [[https://tools.ietf.org/html/rfc2046#section-5.1]]
+ */
+
+public enum Geary.Mime.MultipartSubtype {
+    /**
+     * Used as a placeholder for no or unknown multipart subtype.
+     *
+     * Technically an unknown or unspecified subtype should be treated as { link MIXED}, but there
+     * are situations in code where this is useful.
+     */
+    UNSPECIFIED,
+    /**
+     * A multipart structure of mixed media.
+     *
+     * "Any 'multipart' subtypes that an implementation does not recognize must be treated as
+     * being of subtype 'mixed'."
+     *
+     * See [[https://tools.ietf.org/html/rfc2046#section-5.1.3]]
+     */
+    MIXED,
+     /**
+      * A multipart structure of alternative media.
+      *
+      * See [[https://tools.ietf.org/html/rfc2046#section-5.1.4]]
+      */
+    ALTERNATIVE,
+     /**
+      * A multipart structure of related media.
+      *
+      * See [[http://tools.ietf.org/html/rfc2387]]
+      */
+    RELATED;
+    
+    /**
+     * Converts a { link ContentType} into a { link MultipartSubtype}.
+     *
+     * If unknown, { link MIXED} is returned but is_unknown will be true.
+     */
+    public static MultipartSubtype from_content_type(ContentType? content_type, out bool is_unknown) {
+        if (content_type == null || !content_type.has_media_type("multipart")) {
+            is_unknown = true;
+            
+            return MIXED;
+        }
+        
+        is_unknown = false;
+        switch (Ascii.strdown(content_type.media_subtype)) {
+            case "mixed":
+                return MIXED;
+            
+            case "alternative":
+                return ALTERNATIVE;
+            
+            case "related":
+                return RELATED;
+            
+            default:
+                is_unknown = true;
+                
+                return MIXED;
+        }
+    }
+}
+
diff --git a/src/engine/rfc822/rfc822-message.vala b/src/engine/rfc822/rfc822-message.vala
index 0e2dd09..87df7b9 100644
--- a/src/engine/rfc822/rfc822-message.vala
+++ b/src/engine/rfc822/rfc822-message.vala
@@ -8,6 +8,10 @@ public class Geary.RFC822.Message : BaseObject {
     /**
      * This delegate is an optional parameter to the body constructers that allows callers
      * to process arbitrary non-text, inline MIME parts.
+     *
+     * This is only called for non-text MIME parts in mixed multipart sections.  Inline parts
+     * referred to by rich text in alternative or related documents must be located by the caller
+     * and appropriately presented.
      */
     public delegate string? InlinePartReplacer(string filename, Mime.ContentType? content_type,
         Mime.ContentDisposition? disposition, string? content_id, Geary.Memory.Buffer buffer);
@@ -453,29 +457,39 @@ public class Geary.RFC822.Message : BaseObject {
      * Returns: a bool indicating whether a text part with the desired text_subtype was found
      */
     private bool construct_body_from_mime_parts(ref string? body, InlinePartReplacer? replacer,
-        string text_subtype, bool allow_only_replaced, bool to_html, GMime.Object? node)
+        string text_subtype, bool to_html, GMime.Object? node, Mime.MultipartSubtype container_subtype)
         throws RFC822Error {
         if (node == null) {
             node = message.get_mime_part();
         }
         
+        Mime.ContentType? this_content_type = null;
+        if (node.get_content_type() != null)
+            this_content_type = new Mime.ContentType.from_gmime(node.get_content_type());
+        
+        Mime.MultipartSubtype this_subtype = Mime.MultipartSubtype.from_content_type(this_content_type,
+            null);
+            
         // If this is a multipart, call ourselves recursively on the children
         GMime.Multipart? multipart = node as GMime.Multipart;
         if (multipart != null) {
             bool found_text_subtype = false;
+            
             StringBuilder builder = new StringBuilder();
             int count = multipart.get_count();
             for (int i = 0; i < count; ++i) {
                 GMime.Object child = multipart.get_part(i);
+                
                 string? child_body = null;
                 found_text_subtype |= construct_body_from_mime_parts(ref child_body, replacer,
-                    text_subtype, allow_only_replaced, to_html, child);
+                    text_subtype, to_html, child, this_subtype);
                 if (child_body != null)
                     builder.append(child_body);
             }
             
             if (!Geary.String.is_empty_or_whitespace(builder.str))
                 body = builder.str;
+            
             return found_text_subtype;
         }
         
@@ -495,34 +509,35 @@ public class Geary.RFC822.Message : BaseObject {
         /* Handle text parts that are not attachments
          * They may have inline disposition, or they may have no disposition specified
          */
-        Mime.ContentType? content_type = null;
-        if (part.get_content_type() != null) {
-            content_type = new Mime.ContentType.from_gmime(part.get_content_type());
-            if (content_type.has_media_type("text")) {
-                if (content_type.has_media_subtype(text_subtype)) {
-                    body = mime_part_to_memory_buffer(part, true, to_html).to_string();
-                    return true;
-                }
+        if (this_content_type != null && this_content_type.has_media_type("text")) {
+            if (this_content_type.has_media_subtype(text_subtype)) {
+                body = mime_part_to_memory_buffer(part, true, to_html).to_string();
                 
-                // We were the wrong kind of text part
-                return false;
+                return true;
             }
+            
+            // We were the wrong kind of text part
+            return false;
         }
         
         // If images have no disposition, they are handled elsewhere; see #7299
         if (disposition == null || disposition.disposition_type == Mime.DispositionType.UNSPECIFIED)
             return false;
         
-        if (replacer == null)
+        // Use inline part replacer *only* if in a mixed multipart where each element is to be
+        // presented to the user as structure dictates; for alternative and related, the inline
+        // part is referred to elsewhere in the document and it's the callers responsibility to
+        // locate them
+        if (replacer == null || container_subtype != Mime.MultipartSubtype.MIXED)
             return false;
         
         // Hand off to the replacer for processing
-        string? replaced_part = replacer(RFC822.Utils.get_clean_attachment_filename(part), content_type,
-            disposition, part.get_content_id(), mime_part_to_memory_buffer(part));
+        string? replaced_part = replacer(RFC822.Utils.get_clean_attachment_filename(part),
+            this_content_type, disposition, part.get_content_id(), mime_part_to_memory_buffer(part));
         if (replaced_part != null)
             body = replaced_part;
         
-        return allow_only_replaced && (replaced_part != null);
+        return replaced_part != null;
     }
     
     /**
@@ -537,8 +552,8 @@ public class Geary.RFC822.Message : BaseObject {
     private string? internal_get_body(bool allow_only_replaced, string text_subtype, bool to_html,
         InlinePartReplacer? replacer) throws RFC822Error {
         string? body = null;
-        if (!construct_body_from_mime_parts(ref body, replacer, text_subtype, allow_only_replaced,
-            to_html, null)) {
+        if (!construct_body_from_mime_parts(ref body, replacer, text_subtype, to_html, null,
+            Mime.MultipartSubtype.UNSPECIFIED)) {
             throw new RFC822Error.NOT_FOUND("Could not find any \"text/%s\" parts", text_subtype);
         }
         


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