Skip to content

Commit 822d93c

Browse files
committed
#107: Implemented forwarding, improved replying
#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)
1 parent a1176a7 commit 822d93c

File tree

10 files changed

+656
-430
lines changed

10 files changed

+656
-430
lines changed

Diff for: src/main/java/org/simplejavamail/converter/EmailConverter.java

+34-42
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.simplejavamail.converter.internal.mimemessage.MimeMessageHelper;
44
import org.simplejavamail.converter.internal.mimemessage.MimeMessageParser;
5+
import org.simplejavamail.converter.internal.mimemessage.MimeMessageParser.ParsedMimeMessageComponents;
56
import org.simplejavamail.converter.internal.msgparser.OutlookMessageParser;
67
import org.simplejavamail.email.Email;
78
import org.simplejavamail.email.Recipient;
@@ -14,7 +15,6 @@
1415
import javax.annotation.Nonnull;
1516
import javax.mail.MessagingException;
1617
import javax.mail.Session;
17-
import javax.mail.internet.AddressException;
1818
import javax.mail.internet.InternetAddress;
1919
import javax.mail.internet.MimeMessage;
2020
import java.io.ByteArrayInputStream;
@@ -51,12 +51,9 @@ private EmailConverter() {
5151
* @param mimeMessage The MimeMessage from which to create the {@link Email}.
5252
*/
5353
public static Email mimeMessageToEmail(@Nonnull final MimeMessage mimeMessage) {
54+
checkNonEmptyArgument(mimeMessage, "mimeMessage");
5455
final Email email = new Email(false);
55-
try {
56-
fillEmailFromMimeMessage(email, checkNonEmptyArgument(mimeMessage, "mimeMessage"));
57-
} catch (MessagingException | IOException e) {
58-
throw new EmailConverterException(format(EmailConverterException.PARSE_ERROR_MIMEMESSAGE, e.getMessage()), e);
59-
}
56+
fillEmailFromMimeMessage(email, MimeMessageParser.parseMimeMessage(mimeMessage));
6057
return email;
6158
}
6259

@@ -231,58 +228,53 @@ public static String outlookMsgToEML(@Nonnull final InputStream outloookMsgInput
231228
Helpers
232229
*/
233230

234-
private static void fillEmailFromMimeMessage(@Nonnull final Email email, @Nonnull final MimeMessage mimeMessage)
235-
throws MessagingException, IOException {
231+
private static void fillEmailFromMimeMessage(@Nonnull final Email email, @Nonnull final ParsedMimeMessageComponents parsed) {
236232
checkNonEmptyArgument(email, "email");
237-
checkNonEmptyArgument(mimeMessage, "mimeMessage");
238-
final MimeMessageParser parser = new MimeMessageParser(mimeMessage).parse();
239-
final InternetAddress from = parser.getFrom();
240-
email.setFromAddress(from.getPersonal(), from.getAddress());
241-
final InternetAddress replyTo = parser.getReplyTo();
242-
email.setReplyToAddress(replyTo.getPersonal(), replyTo.getAddress());
243-
for (final Map.Entry<String, Object> header : parser.getHeaders().entrySet()) {
244-
if (!fillPredefinedHeader(email, header)) {
245-
email.addHeader(header.getKey(), header.getValue());
246-
}
233+
checkNonEmptyArgument(parsed, "parsedMimeMessageComponents");
234+
if (parsed.getFromAddress() != null) {
235+
email.setFromAddress(parsed.getFromAddress().getPersonal(), parsed.getFromAddress().getAddress());
236+
}
237+
if (parsed.getReplyToAddresses() != null) {
238+
email.setReplyToAddress(parsed.getReplyToAddresses().getPersonal(), parsed.getReplyToAddresses().getAddress());
239+
}
240+
for (final Map.Entry<String, Object> header : parsed.getHeaders().entrySet()) {
241+
email.addHeader(header.getKey(), header.getValue());
247242
}
248-
email.setId(mimeMessage.getMessageID());
249-
for (final InternetAddress to : parser.getTo()) {
243+
InternetAddress dnTo = parsed.getDispositionNotificationTo();
244+
if (dnTo != null) {
245+
email.setDispositionNotificationTo(new Recipient(dnTo.getPersonal(), dnTo.getAddress(), null));
246+
}
247+
InternetAddress rrTo = parsed.getReturnReceiptTo();
248+
if (rrTo != null) {
249+
email.setReturnReceiptTo(new Recipient(rrTo.getPersonal(), rrTo.getAddress(), null));
250+
}
251+
InternetAddress bTo = parsed.getBounceToAddress();
252+
if (bTo != null) {
253+
email.setBounceToRecipient(new Recipient(bTo.getPersonal(), bTo.getAddress(), null));
254+
}
255+
email.setId(parsed.getMessageId());
256+
for (final InternetAddress to : parsed.getToAddresses()) {
250257
email.addNamedToRecipients(to.getPersonal(), to.getAddress());
251258
}
252259
//noinspection QuestionableName
253-
for (final InternetAddress cc : parser.getCc()) {
260+
for (final InternetAddress cc : parsed.getCcAddresses()) {
254261
email.addNamedCcRecipients(cc.getPersonal(), cc.getAddress());
255262
}
256-
for (final InternetAddress bcc : parser.getBcc()) {
263+
for (final InternetAddress bcc : parsed.getBccAddresses()) {
257264
email.addNamedBccRecipients(bcc.getPersonal(), bcc.getAddress());
258265
}
259-
email.setSubject(parser.getSubject());
260-
email.setText(parser.getPlainContent());
261-
email.setTextHTML(parser.getHtmlContent());
262-
for (final Map.Entry<String, DataSource> cid : parser.getCidMap().entrySet()) {
266+
email.setSubject(parsed.getSubject() != null ? parsed.getSubject() : "");
267+
email.setText(parsed.getPlainContent());
268+
email.setTextHTML(parsed.getHtmlContent());
269+
for (final Map.Entry<String, DataSource> cid : parsed.getCidMap().entrySet()) {
263270
final String cidName = checkNonEmptyArgument(cid.getKey(), "cid.key");
264271
email.addEmbeddedImage(extractCID(cidName), cid.getValue());
265272
}
266-
for (final Map.Entry<String, DataSource> attachment : parser.getAttachmentList().entrySet()) {
273+
for (final Map.Entry<String, DataSource> attachment : parsed.getAttachmentList().entrySet()) {
267274
email.addAttachment(extractCID(attachment.getKey()), attachment.getValue());
268275
}
269276
}
270277

271-
private static boolean fillPredefinedHeader(@Nonnull Email email, @Nonnull Map.Entry<String, Object> header) throws AddressException {
272-
if (header.getKey().equals("Disposition-Notification-To")) {
273-
email.setUseDispositionNotificationTo(true);
274-
InternetAddress internetAddress = new InternetAddress((String) header.getValue());
275-
email.setDispositionNotificationTo(new Recipient(internetAddress.getPersonal(), internetAddress.getAddress(), null));
276-
return true;
277-
} else if (header.getKey().equals("Return-Receipt-To")) {
278-
email.setUseReturnReceiptTo(true);
279-
InternetAddress internetAddress = new InternetAddress((String) header.getValue());
280-
email.setReturnReceiptTo(new Recipient(internetAddress.getPersonal(), internetAddress.getAddress(), null));
281-
return true;
282-
}
283-
return false;
284-
}
285-
286278
private static void fillEmailFromOutlookMessage(@Nonnull final Email email, @Nonnull final OutlookMessage outlookMessage) {
287279
checkNonEmptyArgument(email, "email");
288280
checkNonEmptyArgument(outlookMessage, "outlookMessage");

Diff for: src/main/java/org/simplejavamail/converter/EmailConverterException.java

-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ class EmailConverterException extends MailException {
1212

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

15-
static final String PARSE_ERROR_MIMEMESSAGE = "Error parsing MimeMessage: %s";
16-
1715
public EmailConverterException(final String message, final Exception cause) {
1816
super(message, cause);
1917
}

Diff for: src/main/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageException.java

-23
This file was deleted.

Diff for: src/main/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageHelper.java

+33-11
Original file line numberDiff line numberDiff line change
@@ -89,19 +89,20 @@ protected void updateMessageID() throws MessagingException {
8989
setRecipients(email, message);
9090
// fill multipart structure
9191
setTexts(email, messageRoot.multipartAlternativeMessages);
92+
configureForwarding(email, messageRoot.multipartRootMixed);
9293
setEmbeddedImages(email, messageRoot.multipartRelated);
93-
setAttachments(email, messageRoot.multipartRoot);
94-
message.setContent(messageRoot.multipartRoot);
94+
setAttachments(email, messageRoot.multipartRootMixed);
95+
message.setContent(messageRoot.multipartRootMixed);
9596
setHeaders(email, message);
9697
message.setSentDate(new Date());
9798

9899
if (email.isApplyDKIMSignature()) {
99100
return signMessageWithDKIM(message, email);
100101
}
101-
102+
102103
return message;
103104
}
104-
105+
105106
/**
106107
* Fills the {@link Message} instance with recipients from the {@link Email}.
107108
*
@@ -157,6 +158,24 @@ private static void setTexts(final Email email, final MimeMultipart multipartAlt
157158
multipartAlternativeMessages.addBodyPart(messagePartHTML);
158159
}
159160
}
161+
162+
/**
163+
* If provided, adds the {@code emailToForward} as a MimeBodyPart to the mixed multipart root.
164+
* <p>
165+
* <strong>Note:</strong> this is done without setting {@code Content-Disposition} so email clients can choose
166+
* how to display embedded forwards. Most client will show the forward as inline, some may show it as attachment.
167+
*/
168+
private static void configureForwarding(@Nonnull final Email email, @Nonnull final MimeMultipart multipartRootMixed) {
169+
if (email.getEmailToForward() != null) {
170+
try {
171+
final BodyPart fordwardedMessage = new MimeBodyPart();
172+
fordwardedMessage.setContent(email.getEmailToForward(), "message/rfc822");
173+
multipartRootMixed.addBodyPart(fordwardedMessage);
174+
} catch (MessagingException e) {
175+
e.printStackTrace();
176+
}
177+
}
178+
}
160179

161180
/**
162181
* Fills the {@link Message} instance with the embedded images from the {@link Email}.
@@ -307,7 +326,7 @@ public static MimeMessage signMessageWithDKIM(final MimeMessage message, final E
307326
dkimSigner.setZParam(false);
308327
return new DkimMessage(message, dkimSigner);
309328
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException | MessagingException e) {
310-
throw new MimeMessageException(MimeMessageException.INVALID_DOMAINKEY, e);
329+
throw new MimeMessageParseException(MimeMessageParseException.INVALID_DOMAINKEY, e);
311330
}
312331
}
313332

@@ -316,44 +335,47 @@ public static MimeMessage signMessageWithDKIM(final MimeMessage message, final E
316335
* using JavaMail.<br> <br> The constructor creates a new email message constructed from {@link MimeMultipart} as follows:
317336
* <p/>
318337
* <pre>
319-
* - root
338+
* - mixed root
320339
* - related
321340
* - alternative
322341
* - mail tekst
323342
* - mail html tekst
324343
* - embedded images
344+
* - forwarded message
325345
* - attachments
326346
* </pre>
327347
*
328348
* @author Benny Bottema
329349
*/
330350
private static class MimeEmailMessageWrapper {
331351

332-
private final MimeMultipart multipartRoot;
352+
private final MimeMultipart multipartRootMixed;
333353

334354
private final MimeMultipart multipartRelated;
335355

336356
private final MimeMultipart multipartAlternativeMessages;
337357

338358
/**
339359
* Creates an email skeleton structure, so that embedded images, attachments and (html) texts are being processed properly.
360+
*
361+
* 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>.
340362
*/
341363
MimeEmailMessageWrapper() {
342-
multipartRoot = new MimeMultipart("mixed");
364+
multipartRootMixed = new MimeMultipart("mixed");
343365
final MimeBodyPart contentRelated = new MimeBodyPart();
344366
multipartRelated = new MimeMultipart("related");
345367
final MimeBodyPart contentAlternativeMessages = new MimeBodyPart();
346368
multipartAlternativeMessages = new MimeMultipart("alternative");
347369
try {
348370
// construct mail structure
349-
multipartRoot.addBodyPart(contentRelated);
371+
multipartRootMixed.addBodyPart(contentRelated);
350372
contentRelated.setContent(multipartRelated);
351373
multipartRelated.addBodyPart(contentAlternativeMessages);
352374
contentAlternativeMessages.setContent(multipartAlternativeMessages);
353375
} catch (final MessagingException e) {
354-
throw new MimeMessageException(e.getMessage(), e);
376+
throw new MimeMessageParseException(e.getMessage(), e);
355377
}
356378
}
357379

358380
}
359-
}
381+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.simplejavamail.converter.internal.mimemessage;
2+
3+
import org.simplejavamail.MailException;
4+
5+
import javax.annotation.Nonnull;
6+
import javax.annotation.Nullable;
7+
8+
import static org.simplejavamail.internal.util.Preconditions.checkNonEmptyArgument;
9+
10+
/**
11+
* This exception is used to communicate errors during parsing of a {@link javax.mail.internet.MimeMessage}.
12+
*
13+
* @author Benny Bottema
14+
*/
15+
@SuppressWarnings("serial")
16+
class MimeMessageParseException extends MailException {
17+
18+
static final String INVALID_DOMAINKEY = "Error signing MimeMessage with DKIM";
19+
static final String ERROR_PARSING_FROMADDRESS = "Error parsing from-address";
20+
static final String ERROR_PARSING_ADDRESS = "Error parsing [%s] address";
21+
static final String ERROR_PARSING_DISPOSITION = "Error parsing MimeMessage disposition";
22+
static final String ERROR_PARSING_CONTENT = "Error parsing MimeMessage Content";
23+
static final String ERROR_PARSING_MULTIPART_COUNT = "Error parsing MimeMessage multipart count";
24+
static final String ERROR_GETTING_BODYPART_AT_INDEX = "Error getting bodypart at index %s";
25+
static final String ERROR_GETTING_CONTENT_ID = "Error getting content ID";
26+
static final String ERROR_GETTING_FILENAME = "Error getting file name";
27+
static final String ERROR_GETTING_ALL_HEADERS = "Error getting all headers";
28+
static final String ERROR_GETTING_DATAHANDLER = "Error getting data handler";
29+
static final String ERROR_GETTING_CONTENT_TYPE = "Error getting content type";
30+
static final String ERROR_GETTING_INPUTSTREAM = "Error getting input stream";
31+
static final String ERROR_READING_CONTENT = "Error reading content";
32+
static final String ERROR_DECODING_TEXT = "Error decoding text";
33+
static final String ERROR_GETTING_RECIPIENTS = "Error getting [%s] recipient types";
34+
static final String ERROR_GETTING_SUBJECT = "Error getting subject";
35+
static final String ERROR_GETTING_MESSAGE_ID = "Error getting message ID";
36+
static final String ERROR_PARSING_REPLY_TO_ADDRESSES = "Error parsing replyTo addresses";
37+
38+
MimeMessageParseException(@Nonnull final String message, @Nullable final Exception cause) {
39+
super(checkNonEmptyArgument(message, "message"), cause);
40+
}
41+
}

0 commit comments

Comments
 (0)