[geary] Add ContentType methods for determining file name extenions and type sniffing.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary] Add ContentType methods for determining file name extenions and type sniffing.
- Date: Wed, 15 Feb 2017 22:23:11 +0000 (UTC)
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]