Add unit tests for Geary.RFC822.Message body content, fix a few issues.

* src/engine/rfc822/rfc822-message.vala (Message): Fix has_plain_body(),
  handle the case where displayed MIME entities (as opposed to attached
  ones) with no Content-Type default to US-ASCII, per the RFC.

* test/engine/rfc822-message-test.vala (MessageTest): Add tests for
  testing and accessing body content as both plain text and HTML. Use
  GResources for accessing test message bodies rather than extremely long
  const strings.
This commit is contained in:
Michael James Gratton 2018-05-09 17:34:56 +10:00
parent a2a95686b4
commit bfe665d1a0
8 changed files with 248 additions and 62 deletions

View file

@ -471,7 +471,7 @@ public class Geary.RFC822.Message : BaseObject {
* Determines if the message has one or plain text display parts. * Determines if the message has one or plain text display parts.
*/ */
public bool has_plain_body() { public bool has_plain_body() {
return has_body_parts(message.get_mime_part(), "text"); return has_body_parts(message.get_mime_part(), "plain");
} }
/** /**
@ -486,10 +486,16 @@ public class Geary.RFC822.Message : BaseObject {
*/ */
private bool has_body_parts(GMime.Object node, string text_subtype) { private bool has_body_parts(GMime.Object node, string text_subtype) {
bool has_part = false; bool has_part = false;
Mime.ContentType? this_content_type = null;
if (node.get_content_type() != null) // RFC 2045 Section 5.2 allows us to assume
this_content_type = // text/plain US-ASCII if no content type is
new Mime.ContentType.from_gmime(node.get_content_type()); // otherwise specified.
Mime.ContentType this_content_type = Mime.ContentType.DISPLAY_DEFAULT;
if (node.get_content_type() != null) {
this_content_type = new Mime.ContentType.from_gmime(
node.get_content_type()
);
}
GMime.Multipart? multipart = node as GMime.Multipart; GMime.Multipart? multipart = node as GMime.Multipart;
if (multipart != null) { if (multipart != null) {
@ -508,8 +514,7 @@ public class Geary.RFC822.Message : BaseObject {
if (disposition == null || if (disposition == null ||
disposition.disposition_type != Mime.DispositionType.ATTACHMENT) { disposition.disposition_type != Mime.DispositionType.ATTACHMENT) {
if (this_content_type != null && if (this_content_type.has_media_type("text") &&
this_content_type.has_media_type("text") &&
this_content_type.has_media_subtype(text_subtype)) { this_content_type.has_media_subtype(text_subtype)) {
has_part = true; has_part = true;
} }
@ -537,10 +542,15 @@ public class Geary.RFC822.Message : BaseObject {
*/ */
private bool construct_body_from_mime_parts(GMime.Object node, Mime.MultipartSubtype container_subtype, private bool construct_body_from_mime_parts(GMime.Object node, Mime.MultipartSubtype container_subtype,
string text_subtype, bool to_html, InlinePartReplacer? replacer, ref string? body) throws RFC822Error { string text_subtype, bool to_html, InlinePartReplacer? replacer, ref string? body) throws RFC822Error {
Mime.ContentType? this_content_type = null; // RFC 2045 Section 5.2 allows us to assume text/plain
if (node.get_content_type() != null) // US-ASCII if no content type is otherwise specified.
this_content_type = new Mime.ContentType.from_gmime(node.get_content_type()); Mime.ContentType this_content_type = Mime.ContentType.DISPLAY_DEFAULT;
if (node.get_content_type() != null) {
this_content_type = new Mime.ContentType.from_gmime(
node.get_content_type()
);
}
// If this is a multipart, call ourselves recursively on the children // If this is a multipart, call ourselves recursively on the children
GMime.Multipart? multipart = node as GMime.Multipart; GMime.Multipart? multipart = node as GMime.Multipart;
if (multipart != null) { if (multipart != null) {

View file

@ -0,0 +1,36 @@
From: Alice <alice@example.net>
Sender: Bob <bob@example.net>
To: Charlie <charlie@example.net>
CC: Dave <dave@example.net>
BCC: Eve <eve@example.net>
Reply-To: \"Alice: Personal Account\" <alice@example.org>
Subject: Re: Basic text/html message
Date: Fri, 21 Nov 1997 10:01:10 -0600
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="=-NJextDaQ1tE2ZGhW9Wm0"
Message-ID: <3456@example.net>
In-Reply-To: <1234@local.machine.example>
References: <1234@local.machine.example>
X-Mailer: Geary Test Suite 1.0
--=-NJextDaQ1tE2ZGhW9Wm0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: quoted-printable
This is the first line.
This is the second line.
=
--=-NJextDaQ1tE2ZGhW9Wm0
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
<P>This is the first line.
<P>This is the second line.
=
--=-NJextDaQ1tE2ZGhW9Wm0--

View file

@ -0,0 +1,18 @@
From: Alice <alice@example.net>
Sender: Bob <bob@example.net>
To: Charlie <charlie@example.net>
CC: Dave <dave@example.net>
BCC: Eve <eve@example.net>
Reply-To: \"Alice: Personal Account\" <alice@example.org>
Subject: Re: Basic text/html message
Date: Fri, 21 Nov 1997 10:01:10 -0600
Content-Type: text/html; charset=UTF-8
Message-ID: <3456@example.net>
In-Reply-To: <1234@local.machine.example>
References: <1234@local.machine.example>
X-Mailer: Geary Test Suite 1.0
<P>This is the first line.
<P>This is the second line.

View file

@ -0,0 +1,17 @@
From: Alice <alice@example.net>
Sender: Bob <bob@example.net>
To: Charlie <charlie@example.net>
CC: Dave <dave@example.net>
BCC: Eve <eve@example.net>
Reply-To: "Alice: Personal Account" <alice@example.org>
Subject: Re: Basic text/plain message
Date: Fri, 21 Nov 1997 10:01:10 -0600
Message-ID: <3456@example.net>
In-Reply-To: <1234@local.machine.example>
References: <1234@local.machine.example>
X-Mailer: Geary Test Suite 1.0
This is the first line.
This is the second line.

3
test/data/meson.build Normal file
View file

@ -0,0 +1,3 @@
geary_test_engine_resources = gnome.compile_resources('org.gnome.GearyTest',
files('org.gnome.GearyTest.gresource.xml'),
)

View file

@ -0,0 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<gresources>
<gresource prefix="/org/gnome/GearyTest">
<file>basic-text-plain.eml</file>
<file>basic-text-html.eml</file>
<file>basic-multipart-alternative.eml</file>
</gresource>
</gresources>

View file

@ -7,29 +7,57 @@
class Geary.RFC822.MessageTest : TestCase { class Geary.RFC822.MessageTest : TestCase {
private const string RESOURCE_URI = "resource:///org/gnome/GearyTest";
private const string BASIC_TEXT_PLAIN = "basic-text-plain.eml";
private const string BASIC_TEXT_HTML = "basic-text-html.eml";
private const string BASIC_MULTIPART_ALTERNATIVE =
"basic-multipart-alternative.eml";
private const string HTML_CONVERSION_TEMPLATE =
"<div class=\"plaintext\" style=\"white-space: pre-wrap;\">%s</div>";
private const string BASIC_PLAIN_BODY = """This is the first line.
This is the second line.
""";
private const string BASIC_HTML_BODY = """<P>This is the first line.
<P>This is the second line.
""";
public MessageTest() { public MessageTest() {
base("Geary.RFC822.MessageTest"); base("Geary.RFC822.MessageTest");
add_test("basic_message_from_buffer", basic_message_from_buffer); add_test("basic_message_from_buffer", basic_message_from_buffer);
add_test("encoded_recipient", encoded_recipient); add_test("encoded_recipient", encoded_recipient);
add_test("duplicate_mailbox", duplicate_mailbox); add_test("duplicate_mailbox", duplicate_mailbox);
add_test("duplicate_message_id", duplicate_message_id); add_test("duplicate_message_id", duplicate_message_id);
add_test("text_plain_as_plain", text_plain_as_plain);
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("multipart_alternative_as_plain",
multipart_alternative_as_plain);
add_test("multipart_alternative_as_converted_html",
multipart_alternative_as_converted_html);
add_test("multipart_alternative_as_html",
multipart_alternative_as_html);
add_test("get_preview", get_preview); add_test("get_preview", get_preview);
} }
public void basic_message_from_buffer() throws Error { public void basic_message_from_buffer() throws Error {
Message? basic = null; Message basic = resource_to_message(BASIC_TEXT_PLAIN);
try {
basic = string_to_message(BASIC_MESSAGE); assert_data(basic.subject, "Re: Basic text/plain message");
} catch (Error err) { assert_addresses(basic.from, "Alice <alice@example.net>");
assert_no_error(err); assert_address(basic.sender, "Bob <bob@example.net>");
} assert_addresses(basic.reply_to, "\"Alice: Personal Account\" <alice@example.org>");
assert_data(basic.subject, "Re: Saying Hello"); assert_addresses(basic.to, "Charlie <charlie@example.net>");
assert_addresses(basic.from, "Mary Smith <mary@example.net>"); assert_addresses(basic.cc, "Dave <dave@example.net>");
assert_address(basic.sender, "Mary Smith Sender <mary@example.net>"); assert_addresses(basic.bcc, "Eve <eve@example.net>");
assert_addresses(basic.reply_to, "\"Mary Smith: Personal Account\" <smith@home.example>");
assert_addresses(basic.to, "John Doe <jdoe@machine.example>");
assert_addresses(basic.cc, "John Doe CC <jdoe@machine.example>");
assert_addresses(basic.bcc, "John Doe BCC <jdoe@machine.example>");
//assert_data(basic.message_id, "<3456@example.net>"); //assert_data(basic.message_id, "<3456@example.net>");
assert_message_id_list(basic.in_reply_to, "<1234@local.machine.example>"); assert_message_id_list(basic.in_reply_to, "<1234@local.machine.example>");
assert_message_id_list(basic.references, "<1234@local.machine.example>"); assert_message_id_list(basic.references, "<1234@local.machine.example>");
@ -38,24 +66,14 @@ class Geary.RFC822.MessageTest : TestCase {
} }
public void encoded_recipient() throws Error { public void encoded_recipient() throws Error {
Message? enc = null; Message enc = string_to_message(ENCODED_TO);
try {
enc = string_to_message(ENCODED_TO);
} catch (Error err) {
assert_no_error(err);
}
// Courtesy Mailsploit https://www.mailsploit.com // Courtesy Mailsploit https://www.mailsploit.com
assert(enc.to[0].name == "potus@whitehouse.gov <test>"); assert(enc.to[0].name == "potus@whitehouse.gov <test>");
} }
public void duplicate_mailbox() throws Error { public void duplicate_mailbox() throws Error {
Message? dup = null; Message dup = string_to_message(DUPLICATE_TO);
try {
dup = string_to_message(DUPLICATE_TO);
} catch (Error err) {
assert_no_error(err);
}
assert(dup.to.size == 2); assert(dup.to.size == 2);
assert_addresses( assert_addresses(
@ -64,12 +82,7 @@ class Geary.RFC822.MessageTest : TestCase {
} }
public void duplicate_message_id() throws Error { public void duplicate_message_id() throws Error {
Message? dup = null; Message dup = string_to_message(DUPLICATE_REFERENCES);
try {
dup = string_to_message(DUPLICATE_REFERENCES);
} catch (Error err) {
assert_no_error(err);
}
assert(dup.references.list.size == 2); assert(dup.references.list.size == 2);
assert_message_id_list( assert_message_id_list(
@ -77,13 +90,84 @@ class Geary.RFC822.MessageTest : TestCase {
); );
} }
public void text_plain_as_plain() throws Error {
Message test = resource_to_message(BASIC_TEXT_PLAIN);
assert_true(test.has_plain_body(), "Expected plain body");
assert_false(test.has_html_body(), "Expected non-html body");
assert_string(BASIC_PLAIN_BODY, test.get_plain_body(false, null));
}
public void text_plain_as_html() throws Error {
Message test = resource_to_message(BASIC_TEXT_PLAIN);
assert_true(test.has_plain_body(), "Expected plain body");
assert_false(test.has_html_body(), "Expected non-html body");
assert_string(
HTML_CONVERSION_TEMPLATE.printf(BASIC_PLAIN_BODY),
test.get_plain_body(true, null)
);
}
public void text_html_as_html() throws Error {
Message test = resource_to_message(BASIC_TEXT_HTML);
assert_true(test.has_html_body(), "Expected html body");
assert_false(test.has_plain_body(), "Expected non-plain body");
assert_string(BASIC_HTML_BODY, test.get_html_body(null));
}
public void text_html_as_plain() throws Error {
Message test = resource_to_message(BASIC_TEXT_HTML);
assert_true(test.has_html_body(), "Expected html body");
assert_false(test.has_plain_body(), "Expected non-plain body");
assert_string(BASIC_HTML_BODY, test.get_html_body(null));
}
public void multipart_alternative_as_plain() throws Error {
Message test = resource_to_message(BASIC_MULTIPART_ALTERNATIVE);
assert_true(test.has_plain_body(), "Expected plain body");
assert_true(test.has_html_body(), "Expected html body");
assert_string(BASIC_PLAIN_BODY, test.get_plain_body(false, null));
}
public void multipart_alternative_as_converted_html() throws Error {
Message test = resource_to_message(BASIC_MULTIPART_ALTERNATIVE);
assert_true(test.has_plain_body(), "Expected plain body");
assert_true(test.has_html_body(), "Expected html body");
assert_string(
HTML_CONVERSION_TEMPLATE.printf(BASIC_PLAIN_BODY),
test.get_plain_body(true, null)
);
}
public void multipart_alternative_as_html() throws Error {
Message test = resource_to_message(BASIC_MULTIPART_ALTERNATIVE);
assert_true(test.has_plain_body(), "Expected plain body");
assert_true(test.has_html_body(), "Expected html body");
assert_string(BASIC_HTML_BODY, test.get_html_body(null));
}
public void get_preview() throws Error { public void get_preview() throws Error {
try { Message multipart_signed = string_to_message(MULTIPART_SIGNED_MESSAGE_TEXT);
Message multipart_signed = string_to_message(MULTIPART_SIGNED_MESSAGE_TEXT);
assert(multipart_signed.get_preview() == MULTIPART_SIGNED_MESSAGE_PREVIEW); assert(multipart_signed.get_preview() == MULTIPART_SIGNED_MESSAGE_PREVIEW);
} catch (Error err) { }
assert_no_error(err);
} private Message resource_to_message(string path) throws Error {
GLib.File resource =
GLib.File.new_for_uri(RESOURCE_URI).resolve_relative_path(path);
uint8[] contents;
resource.load_contents(null, out contents, null);
return new Message.from_buffer(
new Geary.Memory.ByteBuffer(contents, contents.length)
);
} }
private Message string_to_message(string message_text) throws Error { private Message string_to_message(string message_text) throws Error {
@ -92,28 +176,34 @@ class Geary.RFC822.MessageTest : TestCase {
); );
} }
private void assert_data(Geary.MessageData.AbstractMessageData? data, string expected) { private void assert_data(Geary.MessageData.AbstractMessageData? actual,
assert(data != null); string expected)
assert(data.to_string() == expected); throws Error {
assert_non_null(actual, expected);
assert_string(expected, actual.to_string());
} }
private void assert_address(Geary.RFC822.MailboxAddress? address, string expected) { private void assert_address(Geary.RFC822.MailboxAddress? address,
assert(address != null); string expected)
assert(address.to_rfc822_string() == expected); throws Error {
assert_non_null(address, expected);
assert_string(expected, address.to_rfc822_string());
} }
private void assert_addresses(Geary.RFC822.MailboxAddresses? addresses, string expected) { private void assert_addresses(Geary.RFC822.MailboxAddresses? addresses,
assert(addresses != null); string expected)
assert(addresses.to_rfc822_string() == expected); throws Error {
assert_non_null(addresses, expected);
assert_string(expected, addresses.to_rfc822_string());
} }
private void assert_message_id_list(Geary.RFC822.MessageIDList? ids, string expected) { private void assert_message_id_list(Geary.RFC822.MessageIDList? ids,
assert(ids != null); string expected)
throws Error {
assert_non_null(ids, expected);
assert(ids.to_rfc822_string() == expected); assert(ids.to_rfc822_string() == expected);
} }
private static string BASIC_MESSAGE = "From: Mary Smith <mary@example.net>\r\nSender: Mary Smith Sender <mary@example.net>\r\nTo: John Doe <jdoe@machine.example>\r\nCC: John Doe CC <jdoe@machine.example>\r\nBCC: John Doe BCC <jdoe@machine.example>\r\nReply-To: \"Mary Smith: Personal Account\" <smith@home.example>\r\nSubject: Re: Saying Hello\r\nDate: Fri, 21 Nov 1997 10:01:10 -0600\r\nMessage-ID: <3456@example.net>\r\nIn-Reply-To: <1234@local.machine.example>\r\nReferences: <1234@local.machine.example>\r\nX-Mailer: Geary Test Suite 1.0\r\n\r\nThis is a reply to your hello.\r\n\r\n";
// Courtesy Mailsploit https://www.mailsploit.com // Courtesy Mailsploit https://www.mailsploit.com
private static string ENCODED_TO = "From: Mary Smith <mary@example.net>\r\nTo: =?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3YiIDx0ZXN0Pg==?= <jdoe@machine.example>\r\nSubject: Re: Saying Hello\r\nDate: Fri, 21 Nov 1997 10:01:10 -0600\r\n\r\nThis is a reply to your hello.\r\n\r\n"; private static string ENCODED_TO = "From: Mary Smith <mary@example.net>\r\nTo: =?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3YiIDx0ZXN0Pg==?= <jdoe@machine.example>\r\nSubject: Re: Saying Hello\r\nDate: Fri, 21 Nov 1997 10:01:10 -0600\r\n\r\nThis is a reply to your hello.\r\n\r\n";

View file

@ -1,3 +1,5 @@
subdir('data')
geary_test_lib_sources = [ geary_test_lib_sources = [
'mock-object.vala', 'mock-object.vala',
'test-case.vala', 'test-case.vala',
@ -41,7 +43,9 @@ geary_test_engine_sources = [
'engine/util-inet-test.vala', 'engine/util-inet-test.vala',
'engine/util-js-test.vala', 'engine/util-js-test.vala',
'engine/util-string-test.vala', 'engine/util-string-test.vala',
'engine/util-timeout-manager-test.vala' 'engine/util-timeout-manager-test.vala',
geary_test_engine_resources
] ]
geary_test_client_sources = [ geary_test_client_sources = [