diff --git a/src/com/fsck/k9/mail/internet/MimeMessage.java b/src/com/fsck/k9/mail/internet/MimeMessage.java index 2f69cba39..ce0ea76d6 100644 --- a/src/com/fsck/k9/mail/internet/MimeMessage.java +++ b/src/com/fsck/k9/mail/internet/MimeMessage.java @@ -25,6 +25,7 @@ import org.apache.james.mime4j.stream.Field; import org.apache.james.mime4j.stream.MimeConfig; import org.apache.james.mime4j.util.MimeUtil; +import com.fsck.k9.BuildConfig; import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Body; import com.fsck.k9.mail.BodyPart; @@ -62,20 +63,6 @@ public class MimeMessage extends Message { } - /** - * Parse the given InputStream using Apache Mime4J to build a MimeMessage. - * Nested messages will not be recursively parsed. - * - * @param in - * @throws IOException - * @throws MessagingException - * - * @see #MimeMessage(InputStream in, boolean recurse) - */ - public MimeMessage(InputStream in) throws IOException, MessagingException { - parse(in); - } - /** * Parse the given InputStream using Apache Mime4J to build a MimeMessage. * @@ -88,11 +75,15 @@ public class MimeMessage extends Message { parse(in, recurse); } - protected void parse(InputStream in) throws IOException, MessagingException { + /** + * Parse the given InputStream using Apache Mime4J to build a MimeMessage. + * Does not recurse through nested bodyparts. + */ + public final void parse(InputStream in) throws IOException, MessagingException { parse(in, false); } - protected void parse(InputStream in, boolean recurse) throws IOException, MessagingException { + private void parse(InputStream in, boolean recurse) throws IOException, MessagingException { mHeader.clear(); mFrom = null; mTo = null; @@ -121,8 +112,8 @@ public class MimeMessage extends Message { try { parser.parse(new EOLConvertingInputStream(in)); } catch (MimeException me) { + //TODO wouldn't a MessagingException be better? throw new Error(me); - } } @@ -482,7 +473,7 @@ public class MimeMessage extends Message { } } - class MimeMessageBuilder implements ContentHandler { + private class MimeMessageBuilder implements ContentHandler { private final LinkedList stack = new LinkedList(); public MimeMessageBuilder() { @@ -519,9 +510,6 @@ public class MimeMessage extends Message { expect(Part.class); } - - - public void endHeader() { expect(Part.class); } @@ -571,16 +559,6 @@ public class MimeMessage extends Message { stack.removeFirst(); } - public void epilogue(InputStream is) throws IOException { - expect(MimeMultipart.class); - StringBuilder sb = new StringBuilder(); - int b; - while ((b = is.read()) != -1) { - sb.append((char)b); - } - // ((Multipart) stack.peek()).setEpilogue(sb.toString()); - } - public void preamble(InputStream is) throws IOException { expect(MimeMultipart.class); StringBuilder sb = new StringBuilder(); @@ -589,7 +567,9 @@ public class MimeMessage extends Message { sb.append((char)b); } ((MimeMultipart)stack.peek()).setPreamble(sb.toString()); + } + public void epilogue(InputStream is) throws IOException { } public void raw(InputStream is) throws IOException { diff --git a/tests/src/com/fsck/k9/mail/internet/MimeMessageParseTest.java b/tests/src/com/fsck/k9/mail/internet/MimeMessageParseTest.java new file mode 100644 index 000000000..7f63f9993 --- /dev/null +++ b/tests/src/com/fsck/k9/mail/internet/MimeMessageParseTest.java @@ -0,0 +1,236 @@ +package com.fsck.k9.mail.internet; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.io.IOUtils; + +import com.fsck.k9.mail.Address; +import com.fsck.k9.mail.Body; +import com.fsck.k9.mail.BodyPart; +import com.fsck.k9.mail.Message.RecipientType; +import com.fsck.k9.mail.Multipart; +import com.fsck.k9.mail.internet.MimeMessage; + +import android.test.AndroidTestCase; + +public class MimeMessageParseTest extends AndroidTestCase { + + static { + BinaryTempFileBody.setTempDirectory(new File(System.getProperty("java.io.tmpdir"))); + } + + private static ByteArrayInputStream toStream(String rawMailData) throws Exception { + return new ByteArrayInputStream(rawMailData.getBytes("ISO-8859-1")); + } + + private static MimeMessage parseWithoutRecurse(InputStream data) throws Exception { + return new MimeMessage(data, false); + } + + private static MimeMessage parseWithRecurse(InputStream data) throws Exception { + return new MimeMessage(data, true); + } + + private static void checkAddresses(Address[] actual, String... expected) { + for (int i = 0; i < actual.length; i++) { + assertEquals(actual[i].toEncodedString(), expected[i]); + } + assertEquals(expected.length, actual.length); + } + + private static String printStream(InputStream stream) throws Exception { + return IOUtils.toString(stream, "ISO-8859-1"); + } + + private static List getLeafParts(Body body) { + if (body instanceof Multipart) { + List ret = new ArrayList(); + for (BodyPart child : ((Multipart) body).getBodyParts()) { + ret.addAll(getLeafParts(child.getBody())); + } + return ret; + } else { + return Collections.singletonList(body); + } + } + + private static void checkLeafParts(MimeMessage msg, String... expectedParts) throws Exception { + List actual = new ArrayList(); + for (Body leaf : getLeafParts(msg.getBody())) { + actual.add(printStream(leaf.getInputStream())); + } + assertEquals(Arrays.asList(expectedParts), actual); + } + + public static void testSinglePart7BitNoRecurse() throws Exception { + MimeMessage msg = parseWithoutRecurse(toStream( + "From: \r\n" + + "To: \r\n" + + "Subject: Testmail\r\n" + + "MIME-Version: 1.0\r\n" + + "Content-type: text/plain\r\n" + + "Content-Transfer-Encoding: 7bit\r\n" + + "\r\n" + + "this is some test text.")); + + checkAddresses(msg.getFrom(), "adam@example.org"); + checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org"); + assertEquals("Testmail", msg.getSubject()); + assertEquals("text/plain", msg.getContentType()); + assertEquals("this is some test text.", printStream(msg.getBody().getInputStream())); + } + + public static void testSinglePart8BitRecurse() throws Exception { + MimeMessage msg = parseWithRecurse(toStream( + "From: \r\n" + + "To: \r\n" + + "Subject: Testmail\r\n" + + "MIME-Version: 1.0\r\n" + + "Content-type: text/plain; encoding=ISO-8859-1\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "gefährliche Umlaute")); + + checkAddresses(msg.getFrom(), "adam@example.org"); + checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org"); + assertEquals("Testmail", msg.getSubject()); + assertEquals("text/plain; encoding=ISO-8859-1", msg.getContentType()); + assertEquals("gefährliche Umlaute", printStream(msg.getBody().getInputStream())); + } + + public static void testSinglePartBase64NoRecurse() throws Exception { + MimeMessage msg = parseWithoutRecurse(toStream( + "From: \r\n" + + "To: \r\n" + + "Subject: Testmail\r\n" + + "MIME-Version: 1.0\r\n" + + "Content-type: text/plain\r\n" + + "Content-Transfer-Encoding: base64\r\n" + + "\r\n" + + "dGhpcyBpcyBzb21lIG1vcmUgdGVzdCB0ZXh0Lg==\r\n")); + + checkAddresses(msg.getFrom(), "adam@example.org"); + checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org"); + assertEquals("Testmail", msg.getSubject()); + assertEquals("text/plain", msg.getContentType()); + assertEquals("this is some more test text.", printStream(msg.getBody().getInputStream())); + } + + public static void testMultipartSingleLayerNoRecurse() throws Exception { + MimeMessage msg = parseWithoutRecurse(toStream( + "From: \r\n" + + "To: \r\n" + + "Subject: Testmail 2\r\n" + + "MIME-Version: 1.0\n" + + "Content-Type: multipart/mixed; boundary=frontier\n" + + "\n" + + "This is a message with multiple parts in MIME format.\n" + + "--frontier\n" + + "Content-Type: text/plain\n" + + "\n" + + "This is the body of the message.\n" + + "--frontier\n" + + "Content-Type: application/octet-stream\n" + + "Content-Transfer-Encoding: base64\n" + + "\n" + + "PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\n" + + "Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg=\n" + + "--frontier--")); + + checkAddresses(msg.getFrom(), "x@example.org"); + checkAddresses(msg.getRecipients(RecipientType.TO), "y@example.org"); + assertEquals("Testmail 2", msg.getSubject()); + assertEquals("multipart/mixed; boundary=frontier", msg.getContentType()); + checkLeafParts(msg, + "This is the body of the message.", + "\n" + + " \n" + + " \n" + + " \n" + + "

This is the body of the message.

\n" + + " \n" + + "\n" + + ""); + } + + public static void testMultipartSingleLayerRecurse() throws Exception { + MimeMessage msg = parseWithRecurse(toStream( + "From: \r\n" + + "To: \r\n" + + "Subject: Testmail 2\r\n" + + "MIME-Version: 1.0\n" + + "Content-Type: multipart/mixed; boundary=frontier\n" + + "\n" + + "This is a message with multiple parts in MIME format.\n" + + "--frontier\n" + + "Content-Type: text/plain\n" + + "\n" + + "This is the body of the message.\n" + + "--frontier\n" + + "Content-Type: application/octet-stream\n" + + "Content-Transfer-Encoding: base64\n" + + "\n" + + "PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\n" + + "Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg=\n" + + "--frontier--")); + + checkAddresses(msg.getFrom(), "x@example.org"); + checkAddresses(msg.getRecipients(RecipientType.TO), "y@example.org"); + assertEquals("Testmail 2", msg.getSubject()); + assertEquals("multipart/mixed; boundary=frontier", msg.getContentType()); + checkLeafParts(msg, + "This is the body of the message.", + "\n" + + " \n" + + " \n" + + " \n" + + "

This is the body of the message.

\n" + + " \n" + + "\n" + + ""); + } + + public static void testMultipartTwoLayersRecurse() throws Exception { + MimeMessage msg = parseWithRecurse(toStream( + "From: \r\n" + + "To: \r\n" + + "Subject: Testmail 2\r\n" + + "MIME-Version: 1.0\n" + + "Content-Type: multipart/mixed; boundary=1\n" + + "\n" + + "This is a message with multiple parts in MIME format.\n" + + "--1\n" + + "Content-Type: text/plain\n" + + "\n" + + "some text in the first part\n" + + "--1\n" + + "Content-Type: multipart/alternative; boundary=2\n" + + "\n" + + "--2\n" + + "Content-Type: text/plain\n" + + "\n" + + "alternative 1\n" + + "--2\n" + + "Content-Type: text/plain\n" + + "\n" + + "alternative 2\n" + + "--2--\n" + + "--1--")); + + checkAddresses(msg.getFrom(), "x@example.org"); + checkAddresses(msg.getRecipients(RecipientType.TO), "y@example.org"); + assertEquals("Testmail 2", msg.getSubject()); + assertEquals("multipart/mixed; boundary=1", msg.getContentType()); + checkLeafParts(msg, + "some text in the first part", + "alternative 1", + "alternative 2"); + } + +}