Skip to content

Commit 5d4b1ab

Browse files
committed
#407: process Outlook message headers, just like with MimeMessages
1 parent cef1062 commit 5d4b1ab

File tree

7 files changed

+116
-20
lines changed

7 files changed

+116
-20
lines changed

modules/outlook-module/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
<dependency>
3636
<groupId>org.simplejavamail</groupId>
3737
<artifactId>outlook-message-parser</artifactId>
38-
<version>1.9.4</version>
38+
<version>1.9.6</version>
3939
<scope>compile</scope>
4040
</dependency>
4141
</dependencies>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.simplejavamail.internal.outlooksupport.converter;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
class HeadersToIgnore {
7+
8+
/**
9+
* Contains the headers we will ignore, because either we set the information differently (such as Subject) or we recognize the header as
10+
* interfering or obsolete for new emails).
11+
*/
12+
static final List<String> HEADERS_TO_IGNORE = new ArrayList<>();
13+
14+
static {
15+
// taken from: protected jakarta.mail.internet.InternetHeaders constructor
16+
/*
17+
* When extracting information to create an Email, we're NOT interested in the following headers:
18+
*/
19+
// HEADERS_TO_IGNORE.add("Return-Path"); // bounceTo address
20+
HEADERS_TO_IGNORE.add("Received");
21+
HEADERS_TO_IGNORE.add("Resent-Date");
22+
HEADERS_TO_IGNORE.add("Resent-From");
23+
HEADERS_TO_IGNORE.add("Resent-Sender");
24+
HEADERS_TO_IGNORE.add("Resent-To");
25+
HEADERS_TO_IGNORE.add("Resent-Cc");
26+
HEADERS_TO_IGNORE.add("Resent-Bcc");
27+
HEADERS_TO_IGNORE.add("Resent-Message-Id");
28+
HEADERS_TO_IGNORE.add("Date");
29+
HEADERS_TO_IGNORE.add("From");
30+
HEADERS_TO_IGNORE.add("Sender");
31+
HEADERS_TO_IGNORE.add("Reply-To");
32+
HEADERS_TO_IGNORE.add("To");
33+
HEADERS_TO_IGNORE.add("Cc");
34+
HEADERS_TO_IGNORE.add("Bcc");
35+
HEADERS_TO_IGNORE.add("Message-Id");
36+
// The next two are needed for replying to
37+
// HEADERS_TO_IGNORE.add("In-Reply-To");
38+
// HEADERS_TO_IGNORE.add("References");
39+
HEADERS_TO_IGNORE.add("Subject");
40+
HEADERS_TO_IGNORE.add("Comments");
41+
HEADERS_TO_IGNORE.add("Keywords");
42+
HEADERS_TO_IGNORE.add("Errors-To");
43+
HEADERS_TO_IGNORE.add("MIME-Version");
44+
HEADERS_TO_IGNORE.add("Content-Type");
45+
HEADERS_TO_IGNORE.add("Content-Transfer-Encoding");
46+
HEADERS_TO_IGNORE.add("Content-MD5");
47+
HEADERS_TO_IGNORE.add(":");
48+
HEADERS_TO_IGNORE.add("Content-Length");
49+
HEADERS_TO_IGNORE.add("Status");
50+
// extra headers that should be ignored, which may originate from nested attachments
51+
HEADERS_TO_IGNORE.add("Content-Disposition");
52+
HEADERS_TO_IGNORE.add("size");
53+
HEADERS_TO_IGNORE.add("filename");
54+
HEADERS_TO_IGNORE.add("Content-ID");
55+
HEADERS_TO_IGNORE.add("name");
56+
HEADERS_TO_IGNORE.add("From");
57+
}
58+
}

modules/outlook-module/src/main/java/org/simplejavamail/internal/outlooksupport/converter/OutlookEmailConverter.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import jakarta.mail.MessagingException;
44
import jakarta.mail.internet.MimeMessage;
5+
import jakarta.mail.internet.MimeUtility;
56
import jakarta.mail.util.ByteArrayDataSource;
67
import org.jetbrains.annotations.NotNull;
78
import org.simplejavamail.api.email.Email;
@@ -26,7 +27,9 @@
2627
import java.util.Map;
2728

2829
import static java.util.Optional.ofNullable;
30+
import static org.simplejavamail.internal.outlooksupport.converter.HeadersToIgnore.HEADERS_TO_IGNORE;
2931
import static org.simplejavamail.internal.util.MiscUtil.extractCID;
32+
import static org.simplejavamail.internal.util.MiscUtil.valueNullOrEmpty;
3033
import static org.simplejavamail.internal.util.Preconditions.checkNonEmptyArgument;
3134
import static org.simplejavamail.internal.util.Preconditions.verifyNonnullOrEmpty;
3235
import static org.slf4j.LoggerFactory.getLogger;
@@ -82,6 +85,8 @@ private static EmailFromOutlookMessage buildEmailFromOutlookMessage(
8285
@NotNull final InternalEmailConverter internalEmailConverter) {
8386
checkNonEmptyArgument(builder, "emailBuilder");
8487
checkNonEmptyArgument(outlookMessage, "outlookMessage");
88+
outlookMessage.getHeadersMap()
89+
.forEach((key, value) -> value.forEach(headerValue -> parseHeader(key, MimeUtility.unfold(headerValue), builder)));
8590
String fromEmail = ofNullable(outlookMessage.getFromEmail()).orElse("[email protected]");
8691
builder.from(outlookMessage.getFromName(), fromEmail);
8792
builder.fixingMessageId(outlookMessage.getMessageId());
@@ -124,6 +129,28 @@ private static EmailFromOutlookMessage buildEmailFromOutlookMessage(
124129
return new EmailFromOutlookMessage(builder, new OutlookMessageProxy(outlookMessage));
125130
}
126131

132+
@SuppressWarnings("StatementWithEmptyBody")
133+
private static void parseHeader(final String headerName, final String headerValue, final EmailPopulatingBuilder builder) {
134+
if (isEmailHeader(headerName, headerValue, "Disposition-Notification-To")) {
135+
builder.withDispositionNotificationTo(headerValue);
136+
} else if (isEmailHeader(headerName, headerValue, "Return-Receipt-To")) {
137+
builder.withReturnReceiptTo(headerValue);
138+
} else if (isEmailHeader(headerName, headerValue, "Return-Path")) {
139+
builder.withBounceTo(headerValue);
140+
} else if (!HEADERS_TO_IGNORE.contains(headerName)) {
141+
builder.withHeader(headerName, headerValue);
142+
} else {
143+
// header recognized, but not relevant (see #HEADERS_TO_IGNORE)
144+
}
145+
}
146+
147+
private static boolean isEmailHeader(String name, String value, String emailHeaderName) {
148+
return name.equals(emailHeaderName) &&
149+
!valueNullOrEmpty(value) &&
150+
!valueNullOrEmpty(value.trim()) &&
151+
!value.equals("<>");
152+
}
153+
127154
private static void copyReceiversFromOutlookMessage(@NotNull EmailPopulatingBuilder builder, @NotNull OutlookMessage outlookMessage) {
128155
//noinspection QuestionableName
129156
for (final OutlookRecipient to : outlookMessage.getToRecipients()) {

modules/simple-java-mail/src/main/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageParser.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ private static void parseHeader(final Header header, @NotNull final ParsedMimeMe
210210
if (!parsedComponents.headers.containsKey(header.getName())) {
211211
parsedComponents.headers.put(header.getName(), new ArrayList<>());
212212
}
213-
parsedComponents.headers.get(header.getName()).add(header.getValue());
213+
// FIXME see if we can do without this unfold
214+
parsedComponents.headers.get(header.getName()).add(MimeUtility.unfold(header.getValue()));
214215
} else {
215216
// header recognized, but not relevant (see #HEADERS_TO_IGNORE)
216217
}

modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/MailerHelper.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,9 @@ public static boolean validate(@NotNull final Email email, @Nullable final Email
7676
scanForInjectionAttack(email.getSubject(), "email.subject");
7777
for (final Map.Entry<String, Collection<String>> headerEntry : email.getHeaders().entrySet()) {
7878
for (final String headerValue : headerEntry.getValue()) {
79-
scanForInjectionAttack(headerEntry.getKey(), "email.header.mapEntryKey");
80-
if (headerEntry.getKey().equals("References")) {
81-
scanForInjectionAttack(MimeUtility.unfold(headerValue), "email.header.References");
82-
} else {
83-
scanForInjectionAttack(headerValue, "email.header." + headerEntry.getKey());
84-
}
79+
// FIXME is this still needed?
80+
scanForInjectionAttack(headerEntry.getKey(), "email.header.headerName");
81+
scanForInjectionAttack(MimeUtility.unfold(headerValue), "email.header." + headerEntry.getKey());
8582
}
8683
}
8784
for (final AttachmentResource attachment : email.getAttachments()) {

modules/simple-java-mail/src/test/java/org/simplejavamail/converter/EmailConverterTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616

1717
import java.io.File;
1818
import java.io.IOException;
19-
import java.nio.charset.Charset;
2019
import java.util.ArrayList;
2120
import java.util.List;
2221

2322
import static demo.ResourceFolderHelper.determineResourceFolder;
2423
import static jakarta.mail.Message.RecipientType.CC;
2524
import static jakarta.mail.Message.RecipientType.TO;
2625
import static java.nio.charset.Charset.defaultCharset;
26+
import static java.util.Collections.singletonList;
2727
import static org.assertj.core.api.Assertions.assertThat;
2828
import static org.simplejavamail.api.email.ContentTransferEncoding.BIT7;
2929
import static org.simplejavamail.internal.util.MiscUtil.normalizeNewlines;
@@ -44,6 +44,7 @@ public void testOutlookBasicConversions() {
4444
EmailAssert.assertThat(msg).hasSubject("Test E-Mail");
4545
EmailAssert.assertThat(msg).hasOnlyRecipients(sven, niklas);
4646
EmailAssert.assertThat(msg).hasNoAttachments();
47+
assertThat(msg.getHeaders()).containsEntry("x-pmx-scanned", singletonList("Mail was scanned by Sophos Pure Message"));
4748
assertThat(msg.getPlainText()).isNotEmpty();
4849
assertThat(normalizeNewlines(msg.getHTMLText())).isEqualTo("<div dir=\"auto\">Just a test to get an email with one cc recipient.</div>\n");
4950
assertThat(normalizeNewlines(msg.getPlainText())).isEqualTo("Just a test to get an email with one cc recipient.\n");

modules/simple-java-mail/src/test/java/org/simplejavamail/mailer/MailerLiveTest.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
import jakarta.mail.internet.MimeMessage;
55
import jakarta.mail.internet.MimeUtility;
66
import lombok.val;
7+
import org.jetbrains.annotations.NotNull;
78
import org.junit.Before;
89
import org.junit.Rule;
910
import org.junit.Test;
1011
import org.simplejavamail.api.email.AttachmentResource;
11-
import org.simplejavamail.api.email.ContentTransferEncoding;
1212
import org.simplejavamail.api.email.Email;
1313
import org.simplejavamail.api.email.EmailAssert;
1414
import org.simplejavamail.api.email.EmailPopulatingBuilder;
@@ -34,7 +34,6 @@
3434
import java.util.GregorianCalendar;
3535
import java.util.List;
3636
import java.util.concurrent.ExecutionException;
37-
import java.util.stream.Collectors;
3837

3938
import static demo.ResourceFolderHelper.determineResourceFolder;
4039
import static jakarta.mail.Message.RecipientType.TO;
@@ -358,12 +357,17 @@ private Email assertSendingEmail(final EmailPopulatingBuilder originalEmailPopul
358357
}
359358
// Jakarta Mail defaults to 7Bit Content-Transfer-Encoding for text attachments, so we need to match that
360359
if (!originalEmailPopulatingBuilder.getAttachments().isEmpty()) {
361-
val attachments = originalEmailPopulatingBuilder.getAttachments().stream()
362-
.map(att -> new AttachmentResource(att.getName(), att.getDataSource(), att.getDescription(), ofNullable(att.getContentTransferEncoding()).orElse(BIT7)))
363-
.collect(toList());
364-
originalEmailPopulatingBuilder
365-
.clearAttachments()
366-
.withAttachments(attachments);
360+
val attachments = fixAttachmentResourcesWith7Bit(originalEmailPopulatingBuilder.getAttachments());
361+
originalEmailPopulatingBuilder.clearAttachments().withAttachments(attachments);
362+
((InternalEmailPopulatingBuilder) originalEmailPopulatingBuilder)
363+
.clearDecryptedAttachments()
364+
.withDecryptedAttachments(attachments);
365+
}
366+
if (!originalEmailPopulatingBuilder.getDecryptedAttachments().isEmpty()) {
367+
val decryptedAttachments = fixAttachmentResourcesWith7Bit(originalEmailPopulatingBuilder.getDecryptedAttachments());
368+
((InternalEmailPopulatingBuilder) originalEmailPopulatingBuilder)
369+
.clearDecryptedAttachments()
370+
.withDecryptedAttachments(decryptedAttachments);
367371
}
368372

369373
if (originalEmailPopulatingBuilder.getOriginalSmimeDetails() instanceof PlainSmimeDetails) {
@@ -382,7 +386,14 @@ private Email assertSendingEmail(final EmailPopulatingBuilder originalEmailPopul
382386

383387
return receivedEmail;
384388
}
385-
389+
390+
@NotNull
391+
private List<AttachmentResource> fixAttachmentResourcesWith7Bit(final List<AttachmentResource> originalEmailPopulatingBuilder) {
392+
return originalEmailPopulatingBuilder.stream()
393+
.map(att -> new AttachmentResource(att.getName(), att.getDataSource(), att.getDescription(), ofNullable(att.getContentTransferEncoding()).orElse(BIT7)))
394+
.collect(toList());
395+
}
396+
386397
@Test
387398
public void createMailSession_ReplyToMessage()
388399
throws MessagingException, ExecutionException, InterruptedException {
@@ -457,10 +468,11 @@ public void createMailSession_ReplyToMessage_NotAll_AndCustomReferences()
457468
EmailAssert.assertThat(receivedReplyToReply).hasOnlyRecipients(new Recipient("Moo Shmoo", "[email protected]", TO));
458469
assertThat(receivedReplyToReply.getHeaders()).contains(entry("In-Reply-To", singletonList(receivedEmailReplyPopulatingBuilder.getId())));
459470

471+
// FIXME revert this to folded check?
460472
assertThat(receivedReplyToReply.getHeaders()).contains(entry("References",
461-
singletonList(MimeUtility.fold("References: ".length(), format("%s\n%s",
473+
singletonList(format("%s %s",
462474
receivedEmailPopulatingBuilder.getId(),
463-
receivedEmailReplyPopulatingBuilder.getId())))
475+
receivedEmailReplyPopulatingBuilder.getId()))
464476
));
465477
}
466478

0 commit comments

Comments
 (0)