Skip to content

Commit

Permalink
#107: Implemented forwarding, improved replying
Browse files Browse the repository at this point in the history
#102: When parsing MimeMessage to Email, also include the Return-Path as bounceToAddress
#95: When parsing MimeMessage to Email, also include the Return-Receipt-To
#93: When parsing MimeMessage to Email, also include the Disposition-Notification-To
Other: Overhauled MimeMessageParser to be more useful and usable (including better exceptions)
  • Loading branch information
bbottema committed Nov 12, 2017
1 parent a1176a7 commit 822d93c
Show file tree
Hide file tree
Showing 10 changed files with 656 additions and 430 deletions.
76 changes: 34 additions & 42 deletions src/main/java/org/simplejavamail/converter/EmailConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.simplejavamail.converter.internal.mimemessage.MimeMessageHelper;
import org.simplejavamail.converter.internal.mimemessage.MimeMessageParser;
import org.simplejavamail.converter.internal.mimemessage.MimeMessageParser.ParsedMimeMessageComponents;
import org.simplejavamail.converter.internal.msgparser.OutlookMessageParser;
import org.simplejavamail.email.Email;
import org.simplejavamail.email.Recipient;
Expand All @@ -14,7 +15,6 @@
import javax.annotation.Nonnull;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.ByteArrayInputStream;
Expand Down Expand Up @@ -51,12 +51,9 @@ private EmailConverter() {
* @param mimeMessage The MimeMessage from which to create the {@link Email}.
*/
public static Email mimeMessageToEmail(@Nonnull final MimeMessage mimeMessage) {
checkNonEmptyArgument(mimeMessage, "mimeMessage");
final Email email = new Email(false);
try {
fillEmailFromMimeMessage(email, checkNonEmptyArgument(mimeMessage, "mimeMessage"));
} catch (MessagingException | IOException e) {
throw new EmailConverterException(format(EmailConverterException.PARSE_ERROR_MIMEMESSAGE, e.getMessage()), e);
}
fillEmailFromMimeMessage(email, MimeMessageParser.parseMimeMessage(mimeMessage));
return email;
}

Expand Down Expand Up @@ -231,58 +228,53 @@ public static String outlookMsgToEML(@Nonnull final InputStream outloookMsgInput
Helpers
*/

private static void fillEmailFromMimeMessage(@Nonnull final Email email, @Nonnull final MimeMessage mimeMessage)
throws MessagingException, IOException {
private static void fillEmailFromMimeMessage(@Nonnull final Email email, @Nonnull final ParsedMimeMessageComponents parsed) {
checkNonEmptyArgument(email, "email");
checkNonEmptyArgument(mimeMessage, "mimeMessage");
final MimeMessageParser parser = new MimeMessageParser(mimeMessage).parse();
final InternetAddress from = parser.getFrom();
email.setFromAddress(from.getPersonal(), from.getAddress());
final InternetAddress replyTo = parser.getReplyTo();
email.setReplyToAddress(replyTo.getPersonal(), replyTo.getAddress());
for (final Map.Entry<String, Object> header : parser.getHeaders().entrySet()) {
if (!fillPredefinedHeader(email, header)) {
email.addHeader(header.getKey(), header.getValue());
}
checkNonEmptyArgument(parsed, "parsedMimeMessageComponents");
if (parsed.getFromAddress() != null) {
email.setFromAddress(parsed.getFromAddress().getPersonal(), parsed.getFromAddress().getAddress());
}
if (parsed.getReplyToAddresses() != null) {
email.setReplyToAddress(parsed.getReplyToAddresses().getPersonal(), parsed.getReplyToAddresses().getAddress());
}
for (final Map.Entry<String, Object> header : parsed.getHeaders().entrySet()) {
email.addHeader(header.getKey(), header.getValue());
}
email.setId(mimeMessage.getMessageID());
for (final InternetAddress to : parser.getTo()) {
InternetAddress dnTo = parsed.getDispositionNotificationTo();
if (dnTo != null) {
email.setDispositionNotificationTo(new Recipient(dnTo.getPersonal(), dnTo.getAddress(), null));
}
InternetAddress rrTo = parsed.getReturnReceiptTo();
if (rrTo != null) {
email.setReturnReceiptTo(new Recipient(rrTo.getPersonal(), rrTo.getAddress(), null));
}
InternetAddress bTo = parsed.getBounceToAddress();
if (bTo != null) {
email.setBounceToRecipient(new Recipient(bTo.getPersonal(), bTo.getAddress(), null));
}
email.setId(parsed.getMessageId());
for (final InternetAddress to : parsed.getToAddresses()) {
email.addNamedToRecipients(to.getPersonal(), to.getAddress());
}
//noinspection QuestionableName
for (final InternetAddress cc : parser.getCc()) {
for (final InternetAddress cc : parsed.getCcAddresses()) {
email.addNamedCcRecipients(cc.getPersonal(), cc.getAddress());
}
for (final InternetAddress bcc : parser.getBcc()) {
for (final InternetAddress bcc : parsed.getBccAddresses()) {
email.addNamedBccRecipients(bcc.getPersonal(), bcc.getAddress());
}
email.setSubject(parser.getSubject());
email.setText(parser.getPlainContent());
email.setTextHTML(parser.getHtmlContent());
for (final Map.Entry<String, DataSource> cid : parser.getCidMap().entrySet()) {
email.setSubject(parsed.getSubject() != null ? parsed.getSubject() : "");
email.setText(parsed.getPlainContent());
email.setTextHTML(parsed.getHtmlContent());
for (final Map.Entry<String, DataSource> cid : parsed.getCidMap().entrySet()) {
final String cidName = checkNonEmptyArgument(cid.getKey(), "cid.key");
email.addEmbeddedImage(extractCID(cidName), cid.getValue());
}
for (final Map.Entry<String, DataSource> attachment : parser.getAttachmentList().entrySet()) {
for (final Map.Entry<String, DataSource> attachment : parsed.getAttachmentList().entrySet()) {
email.addAttachment(extractCID(attachment.getKey()), attachment.getValue());
}
}

private static boolean fillPredefinedHeader(@Nonnull Email email, @Nonnull Map.Entry<String, Object> header) throws AddressException {
if (header.getKey().equals("Disposition-Notification-To")) {
email.setUseDispositionNotificationTo(true);
InternetAddress internetAddress = new InternetAddress((String) header.getValue());
email.setDispositionNotificationTo(new Recipient(internetAddress.getPersonal(), internetAddress.getAddress(), null));
return true;
} else if (header.getKey().equals("Return-Receipt-To")) {
email.setUseReturnReceiptTo(true);
InternetAddress internetAddress = new InternetAddress((String) header.getValue());
email.setReturnReceiptTo(new Recipient(internetAddress.getPersonal(), internetAddress.getAddress(), null));
return true;
}
return false;
}

private static void fillEmailFromOutlookMessage(@Nonnull final Email email, @Nonnull final OutlookMessage outlookMessage) {
checkNonEmptyArgument(email, "email");
checkNonEmptyArgument(outlookMessage, "outlookMessage");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ class EmailConverterException extends MailException {

static final String PARSE_ERROR_EML = "Error parsing EML data: %s";

static final String PARSE_ERROR_MIMEMESSAGE = "Error parsing MimeMessage: %s";

public EmailConverterException(final String message, final Exception cause) {
super(message, cause);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,20 @@ protected void updateMessageID() throws MessagingException {
setRecipients(email, message);
// fill multipart structure
setTexts(email, messageRoot.multipartAlternativeMessages);
configureForwarding(email, messageRoot.multipartRootMixed);
setEmbeddedImages(email, messageRoot.multipartRelated);
setAttachments(email, messageRoot.multipartRoot);
message.setContent(messageRoot.multipartRoot);
setAttachments(email, messageRoot.multipartRootMixed);
message.setContent(messageRoot.multipartRootMixed);
setHeaders(email, message);
message.setSentDate(new Date());

if (email.isApplyDKIMSignature()) {
return signMessageWithDKIM(message, email);
}

return message;
}

/**
* Fills the {@link Message} instance with recipients from the {@link Email}.
*
Expand Down Expand Up @@ -157,6 +158,24 @@ private static void setTexts(final Email email, final MimeMultipart multipartAlt
multipartAlternativeMessages.addBodyPart(messagePartHTML);
}
}

/**
* If provided, adds the {@code emailToForward} as a MimeBodyPart to the mixed multipart root.
* <p>
* <strong>Note:</strong> this is done without setting {@code Content-Disposition} so email clients can choose
* how to display embedded forwards. Most client will show the forward as inline, some may show it as attachment.
*/
private static void configureForwarding(@Nonnull final Email email, @Nonnull final MimeMultipart multipartRootMixed) {
if (email.getEmailToForward() != null) {
try {
final BodyPart fordwardedMessage = new MimeBodyPart();
fordwardedMessage.setContent(email.getEmailToForward(), "message/rfc822");
multipartRootMixed.addBodyPart(fordwardedMessage);
} catch (MessagingException e) {
e.printStackTrace();
}
}
}

/**
* Fills the {@link Message} instance with the embedded images from the {@link Email}.
Expand Down Expand Up @@ -307,7 +326,7 @@ public static MimeMessage signMessageWithDKIM(final MimeMessage message, final E
dkimSigner.setZParam(false);
return new DkimMessage(message, dkimSigner);
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException | MessagingException e) {
throw new MimeMessageException(MimeMessageException.INVALID_DOMAINKEY, e);
throw new MimeMessageParseException(MimeMessageParseException.INVALID_DOMAINKEY, e);
}
}

Expand All @@ -316,44 +335,47 @@ public static MimeMessage signMessageWithDKIM(final MimeMessage message, final E
* using JavaMail.<br> <br> The constructor creates a new email message constructed from {@link MimeMultipart} as follows:
* <p/>
* <pre>
* - root
* - mixed root
* - related
* - alternative
* - mail tekst
* - mail html tekst
* - embedded images
* - forwarded message
* - attachments
* </pre>
*
* @author Benny Bottema
*/
private static class MimeEmailMessageWrapper {

private final MimeMultipart multipartRoot;
private final MimeMultipart multipartRootMixed;

private final MimeMultipart multipartRelated;

private final MimeMultipart multipartAlternativeMessages;

/**
* Creates an email skeleton structure, so that embedded images, attachments and (html) texts are being processed properly.
*
* Some more <a href="https://blogs.technet.microsoft.com/exchange/2011/04/21/mixed-ing-it-up-multipartmixed-messages-and-you/.">helpful reading material</a>.
*/
MimeEmailMessageWrapper() {
multipartRoot = new MimeMultipart("mixed");
multipartRootMixed = new MimeMultipart("mixed");
final MimeBodyPart contentRelated = new MimeBodyPart();
multipartRelated = new MimeMultipart("related");
final MimeBodyPart contentAlternativeMessages = new MimeBodyPart();
multipartAlternativeMessages = new MimeMultipart("alternative");
try {
// construct mail structure
multipartRoot.addBodyPart(contentRelated);
multipartRootMixed.addBodyPart(contentRelated);
contentRelated.setContent(multipartRelated);
multipartRelated.addBodyPart(contentAlternativeMessages);
contentAlternativeMessages.setContent(multipartAlternativeMessages);
} catch (final MessagingException e) {
throw new MimeMessageException(e.getMessage(), e);
throw new MimeMessageParseException(e.getMessage(), e);
}
}

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.simplejavamail.converter.internal.mimemessage;

import org.simplejavamail.MailException;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static org.simplejavamail.internal.util.Preconditions.checkNonEmptyArgument;

/**
* This exception is used to communicate errors during parsing of a {@link javax.mail.internet.MimeMessage}.
*
* @author Benny Bottema
*/
@SuppressWarnings("serial")
class MimeMessageParseException extends MailException {

static final String INVALID_DOMAINKEY = "Error signing MimeMessage with DKIM";
static final String ERROR_PARSING_FROMADDRESS = "Error parsing from-address";
static final String ERROR_PARSING_ADDRESS = "Error parsing [%s] address";
static final String ERROR_PARSING_DISPOSITION = "Error parsing MimeMessage disposition";
static final String ERROR_PARSING_CONTENT = "Error parsing MimeMessage Content";
static final String ERROR_PARSING_MULTIPART_COUNT = "Error parsing MimeMessage multipart count";
static final String ERROR_GETTING_BODYPART_AT_INDEX = "Error getting bodypart at index %s";
static final String ERROR_GETTING_CONTENT_ID = "Error getting content ID";
static final String ERROR_GETTING_FILENAME = "Error getting file name";
static final String ERROR_GETTING_ALL_HEADERS = "Error getting all headers";
static final String ERROR_GETTING_DATAHANDLER = "Error getting data handler";
static final String ERROR_GETTING_CONTENT_TYPE = "Error getting content type";
static final String ERROR_GETTING_INPUTSTREAM = "Error getting input stream";
static final String ERROR_READING_CONTENT = "Error reading content";
static final String ERROR_DECODING_TEXT = "Error decoding text";
static final String ERROR_GETTING_RECIPIENTS = "Error getting [%s] recipient types";
static final String ERROR_GETTING_SUBJECT = "Error getting subject";
static final String ERROR_GETTING_MESSAGE_ID = "Error getting message ID";
static final String ERROR_PARSING_REPLY_TO_ADDRESSES = "Error parsing replyTo addresses";

MimeMessageParseException(@Nonnull final String message, @Nullable final Exception cause) {
super(checkNonEmptyArgument(message, "message"), cause);
}
}
Loading

0 comments on commit 822d93c

Please sign in to comment.