Commit 18fcf0e1 authored by Oliver Giles's avatar Oliver Giles

TNEF (winmail.dat) parsing support via libytnef

parent ef8f9762
......@@ -27,7 +27,7 @@ variables:
gtk3-devel iso-codes-devel json-glib-devel itstool
libcanberra-devel libgee-devel libhandy-devel
libnotify-devel libsecret-devel libunwind-devel
libxml2-devel sqlite-devel webkitgtk4-devel
libxml2-devel libytnef-devel sqlite-devel webkitgtk4-devel
FEDORA_TEST_DEPS: Xvfb tar xz
# Ubuntu packages
......@@ -39,7 +39,7 @@ variables:
libhandy-0.0-dev libjson-glib-dev libmessaging-menu-dev
libnotify-dev libsecret-1-dev libsqlite3-dev
libunity-dev libunwind-dev libwebkit2gtk-4.0-dev
libxml2-dev
libxml2-dev libytnef0-dev
UBUNTU_TEST_DEPS: xauth xvfb
fedora:
......
......@@ -45,7 +45,8 @@ Install them by running this command:
glib2-devel gmime-devel gnome-online-accounts-devel gtk3-devel \
iso-codes-devel json-glib-devel libcanberra-devel \
libgee-devel libhandy-devel libnotify-devel libsecret-devel \
libunwind-devel libxml2-devel sqlite-devel webkitgtk4-devel
libunwind-devel libxml2-devel libytnef-devel sqlite-devel \
webkitgtk4-devel
Installing dependencies on Ubuntu/Debian
----------------------------------------
......@@ -58,7 +59,7 @@ Install them by running this command:
libglib2.0-dev libgmime-2.6-dev libgoa-1.0-dev libgtk-3-dev \
libjson-glib-dev libhandy-dev libnotify-dev libsecret-1-dev \
libsqlite3-dev libunwind-dev libwebkit2gtk-4.0-dev \
libxml2-dev
libxml2-dev libytnef0-dev
And for Ubuntu Unity integration:
......
/*
* Copyright (c) 2018 Oliver Giles <ohw.giles@gmail.com>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
[CCode (cheader_filename = "ytnef.h")]
namespace Ytnef {
[CCode (cname ="variableLength", has_type_id = false)]
public struct VariableLength {
[CCode (array_length_cname = "size")]
uint8[] data;
}
[CCode (cname = "MAPI_UNDEFINED")]
public VariableLength* MAPI_UNDEFINED;
[CCode (cname = "int", cprefix = "PT_", has_type_id = false)]
public enum PropType {
STRING8
}
[CCode (cname = "int", cprefix = "PR_", has_type_id = false)]
public enum PropID {
DISPLAY_NAME,
ATTACH_LONG_FILENAME
}
[CCode (cname = "PROP_TAG")]
public static int PROP_TAG(PropType type, PropID id);
[CCode (cname = "MAPIProps", has_type_id = false)]
public struct MAPIProps {
}
[CCode (cname = "Attachment", has_type_id = false)]
public struct Attachment {
VariableLength Title;
VariableLength FileData;
MAPIProps MAPI;
Attachment? next;
}
[CCode (cname = "TNEFStruct", destroy_function="TNEFFree", has_type_id = false)]
public struct TNEFStruct {
Attachment starting_attach;
}
[CCode (cname = "TNEFParseMemory", has_type_id = false)]
public static int ParseMemory(uint8[] data, out TNEFStruct tnef);
[CCode (cname = "MAPIFindProperty")]
public static unowned VariableLength* MAPIFindProperty(MAPIProps MAPI, uint tag);
}
......@@ -78,6 +78,7 @@ libunwind_generic_dep = dependency(
'libunwind-generic', version: '>= 1.1', required: not get_option('libunwind_optional')
)
libxml = dependency('libxml-2.0', version: '>= 2.7.8')
libytnef = dependency('libytnef', version: '>= 1.9.3', required: get_option('tnef-support'))
posix = valac.find_library('posix')
webkit2gtk_web_extension = dependency('webkit2gtk-web-extension-4.0', version: '>=' + target_webkit)
......
......@@ -5,3 +5,4 @@ option('ref_tracking', type: 'boolean', value: false, description: 'Whether to u
option('iso_639_xml', type: 'string', value: '', description: 'Full path to the ISO 639 XML file.')
option('iso_3166_xml', type: 'string', value: '', description: 'Full path to the ISO 3166 XML file.')
option('libunwind_optional', type: 'boolean', value: false, description: 'Determines if libunwind is required.')
option('tnef-support', type: 'boolean', value: true, description: 'Whether to support TNEF attachments (requires libytnef).')
......@@ -205,6 +205,16 @@
}
]
},
{
"name": "libytnef",
"sources": [
{
"type": "git",
"url": "https://github.com/Yeraze/ytnef.git",
"branch": "master"
}
]
},
{
"name": "geary",
"buildsystem": "meson",
......
......@@ -333,6 +333,7 @@ geary_engine_dependencies = [
gmime,
javascriptcoregtk,
libxml,
libytnef,
posix,
sqlite
]
......@@ -358,6 +359,13 @@ if libunwind_dep.found()
]
endif
if get_option('tnef-support')
geary_engine_dependencies += libytnef
geary_engine_vala_options += [
'-D', 'WITH_TNEF_SUPPORT'
]
endif
geary_engine_lib = static_library('geary-engine',
geary_engine_sources,
dependencies: geary_engine_dependencies,
......
......@@ -880,17 +880,50 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet {
Mime.ContentType content_type =
part.get_effective_content_type();
// Skip text/plain and text/html parts that are INLINE
// or UNSPECIFIED, as they will be included in the body
#if WITH_TNEF_SUPPORT
if (content_type.is_type("application", "vnd.ms-tnef")) {
GMime.StreamMem stream = new GMime.StreamMem();
((GMime.Part) root).get_content_object().write_to_stream(stream);
ByteArray tnef_data = stream.get_byte_array();
Ytnef.TNEFStruct tn;
if (Ytnef.ParseMemory(tnef_data.data, out tn) == 0) {
for (unowned Ytnef.Attachment? a = tn.starting_attach.next; a != null; a = a.next) {
attachments.add(new Part(tnef_attachment_to_gmime_part(a)));
}
}
} else
#endif // WITH_TNEF_SUPPORT
if (actual_disposition == Mime.DispositionType.ATTACHMENT ||
(!content_type.is_type("text", "plain") &&
!content_type.is_type("text", "html"))) {
// Skip text/plain and text/html parts that are INLINE
// or UNSPECIFIED, as they will be included in the body
attachments.add(part);
}
}
}
}
#if WITH_TNEF_SUPPORT
private GMime.Part tnef_attachment_to_gmime_part(Ytnef.Attachment a) {
Ytnef.VariableLength* filenameProp = Ytnef.MAPIFindProperty(a.MAPI, Ytnef.PROP_TAG(Ytnef.PropType.STRING8, Ytnef.PropID.ATTACH_LONG_FILENAME));
if (filenameProp == Ytnef.MAPI_UNDEFINED) {
filenameProp = Ytnef.MAPIFindProperty(a.MAPI, Ytnef.PROP_TAG(Ytnef.PropType.STRING8, Ytnef.PropID.DISPLAY_NAME));
if (filenameProp == Ytnef.MAPI_UNDEFINED) {
filenameProp = &a.Title;
}
}
string filename = (string) filenameProp.data;
uint8[] data = Bytes.unref_to_data(new Bytes(a.FileData.data));
GMime.Part part = new GMime.Part();
part.set_filename(filename);
part.set_content_type(new GMime.ContentType.from_string(GLib.ContentType.guess(filename, data, null)));
part.set_content_object(new GMime.DataWrapper.with_stream(new GMime.StreamMem.with_buffer(data), GMime.ContentEncoding.BINARY));
return part;
}
#endif
public Gee.List<Geary.RFC822.Message> get_sub_messages() {
Gee.List<Geary.RFC822.Message> messages = new Gee.ArrayList<Geary.RFC822.Message>();
find_sub_messages(messages, message.get_mime_part());
......
This diff is collapsed.
......@@ -4,6 +4,7 @@
<file>basic-text-plain.eml</file>
<file>basic-text-html.eml</file>
<file>basic-multipart-alternative.eml</file>
<file>basic-multipart-tnef.eml</file>
<file>geary-0.6-db.tar.xz</file>
</gresource>
</gresources>
......@@ -12,6 +12,7 @@ class Geary.RFC822.MessageTest : TestCase {
private const string BASIC_TEXT_HTML = "basic-text-html.eml";
private const string BASIC_MULTIPART_ALTERNATIVE =
"basic-multipart-alternative.eml";
private const string BASIC_MULTIPART_TNEF = "basic-multipart-tnef.eml";
private const string HTML_CONVERSION_TEMPLATE =
"<div class=\"plaintext\" style=\"white-space: pre-wrap;\">%s</div>";
......@@ -38,6 +39,7 @@ This is the second line.
add_test("text_plain_as_html", text_plain_as_html);
add_test("text_html_as_html", text_html_as_html);
add_test("text_html_as_plain", text_html_as_plain);
add_test("tnef_extract_attachments", tnef_extract_attachments);
add_test("multipart_alternative_as_plain",
multipart_alternative_as_plain);
add_test("multipart_alternative_as_converted_html",
......@@ -124,6 +126,14 @@ This is the second line.
assert_string(BASIC_HTML_BODY, test.get_html_body(null));
}
public void tnef_extract_attachments() throws Error {
Message test = resource_to_message(BASIC_MULTIPART_TNEF);
Gee.List<Part> attachments = test.get_attachments();
assert_true(attachments.size == 2);
assert_true(attachments[0].get_clean_filename() == "zappa_av1.jpg");
assert_true(attachments[1].get_clean_filename() == "bookmark.htm");
}
public void multipart_alternative_as_plain() throws Error {
Message test = resource_to_message(BASIC_MULTIPART_ALTERNATIVE);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment