diff --git a/k9mail/src/main/java/com/fsck/k9/crypto/MessageCryptoStructureDetector.java b/k9mail/src/main/java/com/fsck/k9/crypto/MessageCryptoStructureDetector.java index 974811957..cd8bd7034 100644 --- a/k9mail/src/main/java/com/fsck/k9/crypto/MessageCryptoStructureDetector.java +++ b/k9mail/src/main/java/com/fsck/k9/crypto/MessageCryptoStructureDetector.java @@ -233,7 +233,7 @@ public class MessageCryptoStructureDetector { return dataUnavailable || protocolMatches; } - private static boolean isPartMultipartEncrypted(Part part) { + public static boolean isPartMultipartEncrypted(Part part) { if (!isSameMimeType(part.getMimeType(), MULTIPART_ENCRYPTED)) { return false; } diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfo.java b/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfo.java index f89ff127c..9934447f9 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfo.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfo.java @@ -1,6 +1,7 @@ package com.fsck.k9.mailstore; +import java.util.Collections; import java.util.List; import com.fsck.k9.mail.Message; @@ -36,24 +37,24 @@ public class MessageViewInfo { this.extraAttachments = extraAttachments; } - public static MessageViewInfo createWithExtractedContent( - Message message, boolean isMessageIncomplete, Part rootPart, - String text, List attachments, - CryptoResultAnnotation cryptoResultAnnotation, - AttachmentResolver attachmentResolver, - String extraText, List extraAttachments - ) { + static MessageViewInfo createWithExtractedContent(Message message, boolean isMessageIncomplete, + String text, List attachments, AttachmentResolver attachmentResolver) { return new MessageViewInfo( - message, isMessageIncomplete, rootPart, - text, attachments, - cryptoResultAnnotation, - attachmentResolver, - extraText, extraAttachments - ); + message, isMessageIncomplete, message, text, attachments, null, attachmentResolver, null, + Collections.emptyList()); } public static MessageViewInfo createWithErrorState(Message message, boolean isMessageIncomplete) { return new MessageViewInfo(message, isMessageIncomplete, null, null, null, null, null, null, null); } + MessageViewInfo withCryptoData(CryptoResultAnnotation rootPartAnnotation, String extraViewableText, + List extraAttachmentInfos) { + return new MessageViewInfo( + message, isMessageIncomplete, rootPart, text, attachments, + rootPartAnnotation, + attachmentResolver, + extraViewableText, extraAttachmentInfos + ); + } } diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfoExtractor.java b/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfoExtractor.java index 8d318ca69..feeb06946 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfoExtractor.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfoExtractor.java @@ -12,22 +12,24 @@ import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import com.fsck.k9.Globals; +import com.fsck.k9.K9; import com.fsck.k9.R; +import com.fsck.k9.crypto.MessageCryptoStructureDetector; import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Message; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.Part; import com.fsck.k9.mail.internet.MessageExtractor; +import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.internet.Viewable; import com.fsck.k9.mail.internet.Viewable.Flowed; +import com.fsck.k9.mailstore.CryptoResultAnnotation.CryptoError; import com.fsck.k9.mailstore.util.FlowedMessageUtils; import com.fsck.k9.message.extractors.AttachmentInfoExtractor; import com.fsck.k9.message.html.HtmlConverter; import com.fsck.k9.message.html.HtmlProcessor; import com.fsck.k9.ui.crypto.MessageCryptoAnnotations; -import com.fsck.k9.ui.crypto.MessageCryptoSplitter; -import com.fsck.k9.ui.crypto.MessageCryptoSplitter.CryptoMessageParts; import org.openintents.openpgp.util.OpenPgpUtils; import timber.log.Timber; @@ -69,45 +71,61 @@ public class MessageViewInfoExtractor { } @WorkerThread - public MessageViewInfo extractMessageForView(Message message, @Nullable MessageCryptoAnnotations annotations) + public MessageViewInfo extractMessageForView(Message message, @Nullable MessageCryptoAnnotations cryptoAnnotations) throws MessagingException { - Part rootPart; - CryptoResultAnnotation cryptoResultAnnotation; - List extraParts; + ArrayList extraParts = new ArrayList<>(); + Part cryptoContentPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, extraParts); - CryptoMessageParts cryptoMessageParts = MessageCryptoSplitter.split(message, annotations); - if (cryptoMessageParts != null) { - rootPart = cryptoMessageParts.contentPart; - cryptoResultAnnotation = cryptoMessageParts.contentCryptoAnnotation; - extraParts = cryptoMessageParts.extraParts; - } else { - if (annotations != null && !annotations.isEmpty()) { - Timber.e("Got message annotations but no crypto root part!"); + if (cryptoContentPart == null) { + if (cryptoAnnotations != null && !cryptoAnnotations.isEmpty()) { + Timber.e("Got crypto message cryptoContentAnnotations but no crypto root part!"); } - rootPart = message; - cryptoResultAnnotation = null; - extraParts = null; + return extractSimpleMessageForView(message, message); } - List attachmentInfos = new ArrayList<>(); - ViewableExtractedText viewable = extractViewableAndAttachments( - Collections.singletonList(rootPart), attachmentInfos); + boolean isOpenPgpEncrypted = (MessageCryptoStructureDetector.isPartMultipartEncrypted(cryptoContentPart) && + MessageCryptoStructureDetector.isMultipartEncryptedOpenPgpProtocol(cryptoContentPart)) || + MessageCryptoStructureDetector.isPartPgpInlineEncrypted(cryptoContentPart); + if (!K9.isOpenPgpProviderConfigured() && isOpenPgpEncrypted) { + CryptoResultAnnotation noProviderAnnotation = CryptoResultAnnotation.createErrorAnnotation( + CryptoError.OPENPGP_ENCRYPTED_NO_PROVIDER, null); + return MessageViewInfo.createWithErrorState(message, false) + .withCryptoData(noProviderAnnotation, null, null); + } + + CryptoResultAnnotation cryptoContentPartAnnotation = + cryptoAnnotations != null ? cryptoAnnotations.get(cryptoContentPart) : null; + if (cryptoContentPartAnnotation != null) { + return extractCryptoMessageForView(message, extraParts, cryptoContentPart, cryptoContentPartAnnotation); + } + + return extractSimpleMessageForView(message, message); + } + + private MessageViewInfo extractCryptoMessageForView(Message message, + ArrayList extraParts, Part cryptoContentPart, CryptoResultAnnotation cryptoContentPartAnnotation) + throws MessagingException { + if (cryptoContentPartAnnotation != null && cryptoContentPartAnnotation.hasReplacementData()) { + cryptoContentPart = cryptoContentPartAnnotation.getReplacementData(); + } List extraAttachmentInfos = new ArrayList<>(); - String extraViewableText = null; - if (extraParts != null) { - ViewableExtractedText extraViewable = - extractViewableAndAttachments(extraParts, extraAttachmentInfos); - extraViewableText = extraViewable.text; - } + ViewableExtractedText extraViewable = extractViewableAndAttachments(extraParts, extraAttachmentInfos); - AttachmentResolver attachmentResolver = AttachmentResolver.createFromPart(rootPart); + MessageViewInfo messageViewInfo = extractSimpleMessageForView(message, cryptoContentPart); + return messageViewInfo.withCryptoData(cryptoContentPartAnnotation, extraViewable.text, extraAttachmentInfos); + } - boolean isMessageIncomplete = !message.isSet(Flag.X_DOWNLOADED_FULL) || - MessageExtractor.hasMissingParts(message); + private MessageViewInfo extractSimpleMessageForView(Message message, Part contentPart) throws MessagingException { + List attachmentInfos = new ArrayList<>(); + ViewableExtractedText viewable = extractViewableAndAttachments( + Collections.singletonList(contentPart), attachmentInfos); + AttachmentResolver attachmentResolver = AttachmentResolver.createFromPart(contentPart); + boolean isMessageIncomplete = + !message.isSet(Flag.X_DOWNLOADED_FULL) || MessageExtractor.hasMissingParts(message); - return MessageViewInfo.createWithExtractedContent(message, isMessageIncomplete, rootPart, viewable.html, - attachmentInfos, cryptoResultAnnotation, attachmentResolver, extraViewableText, extraAttachmentInfos); + return MessageViewInfo.createWithExtractedContent( + message, isMessageIncomplete, viewable.html, attachmentInfos, attachmentResolver); } private ViewableExtractedText extractViewableAndAttachments(List parts, diff --git a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoAnnotations.java b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoAnnotations.java index d0e58f129..a450b28d4 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoAnnotations.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoAnnotations.java @@ -12,11 +12,7 @@ import com.fsck.k9.mailstore.CryptoResultAnnotation; public class MessageCryptoAnnotations { private HashMap annotations = new HashMap<>(); - MessageCryptoAnnotations() { - // Package-private constructor - } - - void put(Part part, CryptoResultAnnotation annotation) { + public void put(Part part, CryptoResultAnnotation annotation) { annotations.put(part, annotation); } diff --git a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoSplitter.java b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoSplitter.java deleted file mode 100644 index 02c439eff..000000000 --- a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoSplitter.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.fsck.k9.ui.crypto; - - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import com.fsck.k9.crypto.MessageCryptoStructureDetector; -import com.fsck.k9.mail.Message; -import com.fsck.k9.mail.Part; -import com.fsck.k9.mailstore.CryptoResultAnnotation; -import com.fsck.k9.mailstore.CryptoResultAnnotation.CryptoError; - - -public class MessageCryptoSplitter { - private MessageCryptoSplitter() { } - - @Nullable - public static CryptoMessageParts split(@NonNull Message message, @Nullable MessageCryptoAnnotations annotations) { - ArrayList extraParts = new ArrayList<>(); - Part primaryPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, extraParts); - if (primaryPart == null) { - return null; - } - - if (annotations == null) { - CryptoResultAnnotation rootPartAnnotation = - CryptoResultAnnotation.createErrorAnnotation(CryptoError.OPENPGP_ENCRYPTED_NO_PROVIDER, null); - return new CryptoMessageParts(primaryPart, rootPartAnnotation, extraParts); - } - - CryptoResultAnnotation rootPartAnnotation = annotations.get(primaryPart); - Part rootPart; - if (rootPartAnnotation != null && rootPartAnnotation.hasReplacementData()) { - rootPart = rootPartAnnotation.getReplacementData(); - } else { - rootPart = primaryPart; - } - - return new CryptoMessageParts(rootPart, rootPartAnnotation, extraParts); - } - - public static class CryptoMessageParts { - public final Part contentPart; - @Nullable - public final CryptoResultAnnotation contentCryptoAnnotation; - - public final List extraParts; - - CryptoMessageParts(Part contentPart, @Nullable CryptoResultAnnotation contentCryptoAnnotation, List extraParts) { - this.contentPart = contentPart; - this.contentCryptoAnnotation = contentCryptoAnnotation; - this.extraParts = Collections.unmodifiableList(extraParts); - } - } - -} diff --git a/k9mail/src/test/java/com/fsck/k9/mailstore/MessageViewInfoExtractorTest.java b/k9mail/src/test/java/com/fsck/k9/mailstore/MessageViewInfoExtractorTest.java index 45588eef4..2d799629d 100644 --- a/k9mail/src/test/java/com/fsck/k9/mailstore/MessageViewInfoExtractorTest.java +++ b/k9mail/src/test/java/com/fsck/k9/mailstore/MessageViewInfoExtractorTest.java @@ -9,11 +9,14 @@ import java.util.Locale; import java.util.TimeZone; import android.app.Application; +import android.support.annotation.NonNull; import com.fsck.k9.GlobalsHelper; import com.fsck.k9.K9RobolectricTestRunner; import com.fsck.k9.activity.K9ActivityCommon; import com.fsck.k9.mail.Address; +import com.fsck.k9.mail.BodyPart; +import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message.RecipientType; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.Part; @@ -26,8 +29,11 @@ import com.fsck.k9.mail.internet.MimeMultipart; import com.fsck.k9.mail.internet.TextBody; import com.fsck.k9.mail.internet.Viewable; import com.fsck.k9.mail.internet.Viewable.MessageHeader; +import com.fsck.k9.mailstore.CryptoResultAnnotation.CryptoError; import com.fsck.k9.mailstore.MessageViewInfoExtractor.ViewableExtractedText; +import com.fsck.k9.message.extractors.AttachmentInfoExtractor; import com.fsck.k9.message.html.HtmlProcessor; +import com.fsck.k9.ui.crypto.MessageCryptoAnnotations; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,13 +41,21 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.robolectric.RuntimeEnvironment; +import static com.fsck.k9.message.TestMessageConstructionUtils.bodypart; +import static com.fsck.k9.message.TestMessageConstructionUtils.messageFromBody; +import static com.fsck.k9.message.TestMessageConstructionUtils.multipart; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +@SuppressWarnings("WeakerAccess") @RunWith(K9RobolectricTestRunner.class) public class MessageViewInfoExtractorTest { public static final String BODY_TEXT = "K-9 Mail rocks :>"; @@ -51,6 +65,7 @@ public class MessageViewInfoExtractorTest { private MessageViewInfoExtractor messageViewInfoExtractor; private Application context; + private AttachmentInfoExtractor attachmentInfoExtractor; @Before @@ -60,7 +75,8 @@ public class MessageViewInfoExtractorTest { GlobalsHelper.setContext(context); HtmlProcessor htmlProcessor = createFakeHtmlProcessor(); - messageViewInfoExtractor = new MessageViewInfoExtractor(context,null, htmlProcessor); + attachmentInfoExtractor = spy(AttachmentInfoExtractor.getInstance()); + messageViewInfoExtractor = new MessageViewInfoExtractor(context, attachmentInfoExtractor, htmlProcessor); } @Test @@ -359,6 +375,190 @@ public class MessageViewInfoExtractorTest { assertEquals(expectedHtmlText, firstMessageExtractedText.html); } + @Test + public void extractMessage_withAttachment() throws Exception { + BodyPart attachmentPart = bodypart("application/octet-stream"); + Message message = messageFromBody(multipart("mixed", + bodypart("text/plain", "text"), + attachmentPart + )); + AttachmentViewInfo attachmentViewInfo = mock(AttachmentViewInfo.class); + setupAttachmentInfoForPart(attachmentPart, attachmentViewInfo); + + + MessageViewInfo messageViewInfo = messageViewInfoExtractor.extractMessageForView(message, null); + + + assertEquals("
text
", messageViewInfo.text); + assertSame(attachmentViewInfo, messageViewInfo.attachments.get(0)); + assertNull(messageViewInfo.cryptoResultAnnotation); + assertTrue(messageViewInfo.extraAttachments.isEmpty()); + } + + @Test + public void extractMessage_withCryptoAnnotation() throws Exception { + Message message = messageFromBody(multipart("signed", "protocol=\"application/pgp-signature\"", + bodypart("text/plain", "text"), + bodypart("application/pgp-signature") + )); + CryptoResultAnnotation annotation = CryptoResultAnnotation.createOpenPgpResultAnnotation( + null, null, null, null, null, false); + MessageCryptoAnnotations messageCryptoAnnotations = createAnnotations(message, annotation); + + + MessageViewInfo messageViewInfo = messageViewInfoExtractor.extractMessageForView(message, messageCryptoAnnotations); + + + assertEquals("
text
", messageViewInfo.text); + assertSame(annotation, messageViewInfo.cryptoResultAnnotation); + assertTrue(messageViewInfo.attachments.isEmpty()); + assertTrue(messageViewInfo.extraAttachments.isEmpty()); + } + + @Test + public void extractMessage_withCryptoAnnotation_andReplacementPart() throws Exception { + Message message = messageFromBody(multipart("signed", "protocol=\"application/pgp-signature\"", + bodypart("text/plain", "text"), + bodypart("application/pgp-signature") + )); + MimeBodyPart replacementPart = bodypart("text/plain", "replacement text"); + CryptoResultAnnotation annotation = CryptoResultAnnotation.createOpenPgpResultAnnotation( + null, null, null, null, replacementPart, false); + MessageCryptoAnnotations messageCryptoAnnotations = createAnnotations(message, annotation); + + + MessageViewInfo messageViewInfo = messageViewInfoExtractor.extractMessageForView(message, messageCryptoAnnotations); + + + assertEquals("
replacement text
", messageViewInfo.text); + assertSame(annotation, messageViewInfo.cryptoResultAnnotation); + assertTrue(messageViewInfo.attachments.isEmpty()); + assertTrue(messageViewInfo.extraAttachments.isEmpty()); + } + + @Test + public void extractMessage_withCryptoAnnotation_andExtraText() throws Exception { + MimeBodyPart signedPart = multipart("signed", "protocol=\"application/pgp-signature\"", + bodypart("text/plain", "text"), + bodypart("application/pgp-signature") + ); + BodyPart extraText = bodypart("text/plain", "extra text"); + Message message = messageFromBody(multipart("mixed", + signedPart, + extraText + )); + CryptoResultAnnotation annotation = CryptoResultAnnotation.createOpenPgpResultAnnotation( + null, null, null, null, null, false); + MessageCryptoAnnotations messageCryptoAnnotations = createAnnotations(signedPart, annotation); + + + MessageViewInfo messageViewInfo = messageViewInfoExtractor.extractMessageForView(message, messageCryptoAnnotations); + + + assertEquals("
text
", messageViewInfo.text); + assertSame(annotation, messageViewInfo.cryptoResultAnnotation); + assertEquals("extra text", messageViewInfo.extraText); + assertTrue(messageViewInfo.attachments.isEmpty()); + assertTrue(messageViewInfo.extraAttachments.isEmpty()); + } + + @Test + public void extractMessage_withCryptoAnnotation_andExtraAttachment() throws Exception { + MimeBodyPart signedPart = multipart("signed", "protocol=\"application/pgp-signature\"", + bodypart("text/plain", "text"), + bodypart("application/pgp-signature") + ); + BodyPart extraAttachment = bodypart("application/octet-stream"); + Message message = messageFromBody(multipart("mixed", + signedPart, + extraAttachment + )); + CryptoResultAnnotation annotation = CryptoResultAnnotation.createOpenPgpResultAnnotation( + null, null, null, null, null, false); + MessageCryptoAnnotations messageCryptoAnnotations = createAnnotations(signedPart, annotation); + + AttachmentViewInfo attachmentViewInfo = mock(AttachmentViewInfo.class); + setupAttachmentInfoForPart(extraAttachment, attachmentViewInfo); + + + MessageViewInfo messageViewInfo = messageViewInfoExtractor.extractMessageForView(message, messageCryptoAnnotations); + + + assertEquals("
text
", messageViewInfo.text); + assertSame(annotation, messageViewInfo.cryptoResultAnnotation); + assertSame(attachmentViewInfo, messageViewInfo.extraAttachments.get(0)); + assertTrue(messageViewInfo.attachments.isEmpty()); + } + + @Test + public void extractMessage_openPgpEncrypted_withoutAnnotations() throws Exception { + Message message = messageFromBody( + multipart("encrypted", "protocol=\"application/pgp-encrypted\"", + bodypart("application/pgp-encrypted"), + bodypart("application/octet-stream") + ) + ); + + MessageViewInfo messageViewInfo = messageViewInfoExtractor.extractMessageForView(message, null); + + assertEquals(CryptoError.OPENPGP_ENCRYPTED_NO_PROVIDER, messageViewInfo.cryptoResultAnnotation.getErrorType()); + assertNull(messageViewInfo.text); + assertNull(messageViewInfo.attachments); + assertNull(messageViewInfo.extraAttachments); + } + + @Test + public void extractMessage_multipartSigned_UnknownProtocol() throws Exception { + Message message = messageFromBody( + multipart("signed", "protocol=\"application/pkcs7-signature\"", + bodypart("text/plain", "text"), + bodypart("application/pkcs7-signature", "signature") + ) + ); + + MessageViewInfo messageViewInfo = messageViewInfoExtractor.extractMessageForView(message, null); + + assertEquals("
text
", messageViewInfo.text); + assertNull(messageViewInfo.cryptoResultAnnotation); + assertTrue(messageViewInfo.attachments.isEmpty()); + assertTrue(messageViewInfo.extraAttachments.isEmpty()); + } + + @Test + public void extractMessage_multipartSigned_UnknownProtocol_withExtraAttachments() throws Exception { + BodyPart extraAttachment = bodypart("application/octet-stream"); + Message message = messageFromBody( + multipart("mixed", + multipart("signed", "protocol=\"application/pkcs7-signature\"", + bodypart("text/plain", "text"), + bodypart("application/pkcs7-signature", "signature") + ), + extraAttachment + ) + ); + AttachmentViewInfo mock = mock(AttachmentViewInfo.class); + setupAttachmentInfoForPart(extraAttachment, mock); + + MessageViewInfo messageViewInfo = messageViewInfoExtractor.extractMessageForView(message, null); + + assertEquals("
text
", messageViewInfo.text); + assertNull(messageViewInfo.cryptoResultAnnotation); + assertSame(mock, messageViewInfo.attachments.get(0)); + assertTrue(messageViewInfo.extraAttachments.isEmpty()); + } + + void setupAttachmentInfoForPart(BodyPart extraAttachment, AttachmentViewInfo attachmentViewInfo) + throws MessagingException { + doReturn(attachmentViewInfo).when(attachmentInfoExtractor).extractAttachmentInfo(extraAttachment); + } + + @NonNull + MessageCryptoAnnotations createAnnotations(Part part, CryptoResultAnnotation annotation) { + MessageCryptoAnnotations messageCryptoAnnotations = new MessageCryptoAnnotations(); + messageCryptoAnnotations.put(part, annotation); + return messageCryptoAnnotations; + } + HtmlProcessor createFakeHtmlProcessor() { HtmlProcessor htmlProcessor = mock(HtmlProcessor.class); diff --git a/k9mail/src/test/java/com/fsck/k9/message/TestMessageConstructionUtils.java b/k9mail/src/test/java/com/fsck/k9/message/TestMessageConstructionUtils.java index 45ee4b908..07e813eaa 100644 --- a/k9mail/src/test/java/com/fsck/k9/message/TestMessageConstructionUtils.java +++ b/k9mail/src/test/java/com/fsck/k9/message/TestMessageConstructionUtils.java @@ -45,7 +45,7 @@ public class TestMessageConstructionUtils { return new MimeBodyPart(null, type); } - public static BodyPart bodypart(String type, String text) throws MessagingException { + public static MimeBodyPart bodypart(String type, String text) throws MessagingException { TextBody textBody = new TextBody(text); return new MimeBodyPart(textBody, type); }