[geary] Add ContentType methods for determining file name extenions and type sniffing.



commit e3d708b8e6915b31e2d908ec726bd49cd17deb0b
Author: Michael James Gratton <mike vee net>
Date:   Tue Feb 14 22:26:14 2017 +1100

    Add ContentType methods for determining file name extenions and type sniffing.
    
    * src/engine/mime/mime-content-type.vala (ContentType): Add ::guess_type
      and ::get_file_name_extension methods, unit tests.

 src/engine/mime/mime-content-type.vala  |  102 +++++++++++++++++++++++++-----
 test/CMakeLists.txt                     |    1 +
 test/engine/mime-content-type-test.vala |   56 +++++++++++++++++
 test/main.vala                          |    1 +
 4 files changed, 143 insertions(+), 17 deletions(-)
---
diff --git a/src/engine/mime/mime-content-type.vala b/src/engine/mime/mime-content-type.vala
index 397c0f2..6f7350b 100644
--- a/src/engine/mime/mime-content-type.vala
+++ b/src/engine/mime/mime-content-type.vala
@@ -11,18 +11,91 @@
  */
 
 public class Geary.Mime.ContentType : Geary.BaseObject {
-    /*
+
+    /**
      * MIME wildcard for comparing {@link media_type} and {@link media_subtype}.
      *
      * @see is_type
      */
     public const string WILDCARD = "*";
-    
+
     /**
      * Default Content-Type for unknown or unmarked content.
      */
     public const string DEFAULT_CONTENT_TYPE = "application/octet-stream";
-    
+
+
+    private static Gee.Map<string,string> TYPES_TO_EXTENSIONS =
+        new Gee.HashMap<string,string>();
+
+    static construct {
+        // XXX We should be loading file name extension information
+        // from /etc/mime.types and/or the XDG Shared MIME-info
+        // Database globs2 file, usually located at
+        // "/usr/share/mime/globs2" (See: {@link
+        // https://specifications.freedesktop.org/shared-mime-info-spec/latest/}).
+        //
+        // But for now the most part the only things that we have to
+        // guess this for are inline embeds that don't have filenames,
+        // i.e. images, so we can hopefully get away with the set
+        // below for now.
+        TYPES_TO_EXTENSIONS["image/jpeg"] = ".jpeg";
+        TYPES_TO_EXTENSIONS["image/png"] = ".png";
+        TYPES_TO_EXTENSIONS["image/gif"] = ".gif";
+        TYPES_TO_EXTENSIONS["image/svg+xml"] = ".svg";
+        TYPES_TO_EXTENSIONS["image/bmp"] = ".bmp";
+        TYPES_TO_EXTENSIONS["image/x-bmp"] = ".bmp";
+    }
+
+    public static ContentType deserialize(string str) throws MimeError {
+        // perform a little sanity checking here, as it doesn't appear the GMime constructor has
+        // any error-reporting at all
+        if (String.is_empty(str))
+            throw new MimeError.PARSE("Empty MIME Content-Type");
+
+        if (!str.contains("/"))
+            throw new MimeError.PARSE("Invalid MIME Content-Type: %s", str); 
+
+        return new ContentType.from_gmime(new GMime.ContentType.from_string(str));
+    }
+
+    /**
+     * Attempts to guess the content type for a buffer using GIO sniffing.
+     */
+    public static ContentType guess_type(string? file_name, Geary.Memory.Buffer? buf) throws Error {
+        string? mime_type = null;
+
+        if (file_name != null) {
+            // XXX might just want to use xdgmime lib directly here to
+            // avoid the intermediate glib_content_type step here?
+            string glib_type = GLib.ContentType.guess(file_name, null, null);
+            mime_type = GLib.ContentType.get_mime_type(glib_type);
+            if (Geary.String.is_empty(mime_type)) {
+                mime_type = null;
+            }
+        }
+
+        if (mime_type == null && buf != null) {
+            int max_len = 4096;
+            // XXX determine actual max needed buffer size using
+            // xdg_mime_get_max_buffer_extents?
+            uint8[] data = (max_len > buf.size)
+                ? buf.get_bytes()[0:max_len - 1].get_data()
+                : buf.get_uint8_array();
+
+            // XXX might just want to use xdgmime lib directly here to
+            // avoid the intermediate glib_content_type step here?
+            string glib_type = GLib.ContentType.guess(null, data, null);
+            mime_type = GLib.ContentType.get_mime_type(glib_type);
+        }
+
+        if (Geary.String.is_empty(mime_type)) {
+            mime_type = DEFAULT_CONTENT_TYPE;
+        }
+        return deserialize(mime_type);
+    }
+
+
     /**
      * The type (discrete or concrete) portion of the Content-Type field.
      *
@@ -69,19 +142,7 @@ public class Geary.Mime.ContentType : Geary.BaseObject {
         media_subtype = content_type.get_media_subtype().strip();
         params = new ContentParameters.from_gmime(content_type.get_params());
     }
-    
-    public static ContentType deserialize(string str) throws MimeError {
-        // perform a little sanity checking here, as it doesn't appear the GMime constructor has
-        // any error-reporting at all
-        if (String.is_empty(str))
-            throw new MimeError.PARSE("Empty MIME Content-Type");
-        
-        if (!str.contains("/"))
-            throw new MimeError.PARSE("Invalid MIME Content-Type: %s", str); 
-        
-        return new ContentType.from_gmime(new GMime.ContentType.from_string(str));
-    }
-    
+
     /**
      * Compares the {@link media_type} with the supplied type.
      *
@@ -115,7 +176,14 @@ public class Geary.Mime.ContentType : Geary.BaseObject {
     public string get_mime_type() {
         return "%s/%s".printf(media_type, media_subtype);
     }
-    
+
+    /**
+     * Returns the file name extension for this type, if known.
+     */
+    public string? get_file_name_extension() {
+        return TYPES_TO_EXTENSIONS[get_mime_type()];
+    }
+
     /**
      * Compares the supplied type and subtype with this instance's.
      *
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index be65cfc..70a5e4c 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -6,6 +6,7 @@ set(TEST_SRC
   main.vala
   testcase.vala # Based on same file in libgee, courtesy Julien Peeters
 
+  engine/mime-content-type-test.vala
   engine/rfc822-mailbox-address-test.vala
   engine/rfc822-message-test.vala
   engine/rfc822-message-data-test.vala
diff --git a/test/engine/mime-content-type-test.vala b/test/engine/mime-content-type-test.vala
new file mode 100644
index 0000000..6e4c418
--- /dev/null
+++ b/test/engine/mime-content-type-test.vala
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+class Geary.Mime.ContentTypeTest : Gee.TestCase {
+
+    public ContentTypeTest() {
+        base("Geary.Mime.ContentTypeTest");
+        add_test("get_file_name_extension", get_file_name_extension);
+        add_test("guess_type_from_name", guess_type_from_name);
+        add_test("guess_type_from_buf", guess_type_from_buf);
+    }
+    }
+
+    public void get_file_name_extension() {
+        assert(new ContentType("image", "jpeg", null).get_file_name_extension() == ".jpeg");
+        assert(new ContentType("test", "unknown", null).get_file_name_extension() == null);
+    }
+
+    public void guess_type_from_name() {
+        try {
+            assert(ContentType.guess_type("test.png", null).is_type("image", "png"));
+        } catch (Error err) {
+            assert_not_reached();
+        }
+
+        try {
+            assert(ContentType.guess_type("foo.test", null).get_mime_type() == 
ContentType.DEFAULT_CONTENT_TYPE);
+        } catch (Error err) {
+            assert_not_reached();
+        }
+    }
+
+    public void guess_type_from_buf() {
+        Memory.ByteBuffer png = new Memory.ByteBuffer(
+            {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}, 8 // PNG magic
+        );
+        Memory.ByteBuffer empty = new Memory.ByteBuffer({0x0}, 1);
+
+        try {
+            assert(ContentType.guess_type(null, png).is_type("image", "png"));
+        } catch (Error err) {
+            assert_not_reached();
+        }
+
+        try {
+            assert(ContentType.guess_type(null, empty).get_mime_type() == ContentType.DEFAULT_CONTENT_TYPE);
+        } catch (Error err) {
+            assert_not_reached();
+        }
+    }
+
+}
diff --git a/test/main.vala b/test/main.vala
index 8d4dff1..7472a1a 100644
--- a/test/main.vala
+++ b/test/main.vala
@@ -41,6 +41,7 @@ int main(string[] args) {
     engine.add_suite(new Geary.IdleManagerTest().get_suite());
     engine.add_suite(new Geary.Inet.Test().get_suite());
     engine.add_suite(new Geary.JS.Test().get_suite());
+    engine.add_suite(new Geary.Mime.ContentTypeTest().get_suite());
     engine.add_suite(new Geary.RFC822.MailboxAddressTest().get_suite());
     engine.add_suite(new Geary.RFC822.MessageTest().get_suite());
     engine.add_suite(new Geary.RFC822.MessageDataTest().get_suite());


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