Skip to content

Commit a0f57e2

Browse files
committed
#500: [bug] Fix parsing addresses from headers in EML files, like a Disposition-Notification-To with umlaut
1 parent c752fe9 commit a0f57e2

File tree

5 files changed

+144
-24
lines changed

5 files changed

+144
-24
lines changed

modules/core-module/src/main/java/org/simplejavamail/internal/config/EmailProperty.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.simplejavamail.internal.config;
22

3+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
34
import lombok.Getter;
45
import lombok.RequiredArgsConstructor;
56
import org.jetbrains.annotations.NotNull;
@@ -14,6 +15,7 @@
1415
*/
1516
@RequiredArgsConstructor
1617
@Getter
18+
@SuppressFBWarnings("SE_BAD_FIELD")
1719
public enum EmailProperty {
1820

1921
HEADERS(Email::getHeaders, true),

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

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

33
import jakarta.activation.*;
44
import jakarta.mail.Address;
5+
import jakarta.mail.Header;
56
import jakarta.mail.Message.RecipientType;
67
import jakarta.mail.MessagingException;
78
import jakarta.mail.Multipart;
@@ -29,7 +30,6 @@
2930
import static java.lang.String.format;
3031
import static java.nio.charset.StandardCharsets.UTF_8;
3132
import static java.util.Optional.ofNullable;
32-
import static java.util.stream.Collectors.toList;
3333
import static org.simplejavamail.internal.util.MiscUtil.extractCID;
3434
import static org.simplejavamail.internal.util.MiscUtil.valueNullOrEmpty;
3535
import static org.slf4j.LoggerFactory.getLogger;
@@ -75,7 +75,7 @@ public static ParsedMimeMessageComponents parseMimeMessage(@NotNull final MimeMe
7575
}
7676

7777
private static void parseMimePartTree(@NotNull final MimePart currentPart, @NotNull final ParsedMimeMessageComponents parsedComponents, final boolean fetchAttachmentData) {
78-
for (final DecodedHeader header : retrieveAllHeaders(currentPart)) {
78+
for (final Header header : retrieveAllHeaders(currentPart)) {
7979
parseHeader(header, parsedComponents);
8080
}
8181

@@ -122,8 +122,8 @@ private static void parseDataSource(@NotNull MimePart currentPart, @NotNull Pars
122122

123123
private static void checkContentTransferEncoding(final MimePart currentPart, @NotNull final ParsedMimeMessageComponents parsedComponents) {
124124
if (parsedComponents.contentTransferEncoding == null) {
125-
for (final DecodedHeader header : retrieveAllHeaders(currentPart)) {
126-
if (isEmailHeader(header, "Content-Transfer-Encoding")) {
125+
for (final Header header : retrieveAllHeaders(currentPart)) {
126+
if (isEmailHeader(DecodedHeader.of(header), "Content-Transfer-Encoding")) {
127127
parsedComponents.contentTransferEncoding = header.getValue();
128128
}
129129
}
@@ -139,21 +139,20 @@ private static MimeDataSource parseAttachment(@Nullable final String contentId,
139139
.build();
140140
}
141141

142-
private static void parseHeader(final DecodedHeader header, @NotNull final ParsedMimeMessageComponents parsedComponents) {
143-
val headerValue = decodeText(header.getValue());
144-
val headerName = decodeText(header.getName());
142+
private static void parseHeader(final Header header, @NotNull final ParsedMimeMessageComponents parsedComponents) {
143+
val decodedHeader = DecodedHeader.of(header);
145144

146-
if (isEmailHeader(header, "Disposition-Notification-To")) {
147-
parsedComponents.dispositionNotificationTo = createAddress(headerValue, "Disposition-Notification-To");
148-
} else if (isEmailHeader(header, "Return-Receipt-To")) {
149-
parsedComponents.returnReceiptTo = createAddress(headerValue, "Return-Receipt-To");
150-
} else if (isEmailHeader(header, "Return-Path")) {
151-
parsedComponents.bounceToAddress = createAddress(headerValue, "Return-Path");
145+
if (isEmailHeader(decodedHeader, "Disposition-Notification-To")) {
146+
parsedComponents.dispositionNotificationTo = createAddressFromEncodedHeader(header, "Disposition-Notification-To");
147+
} else if (isEmailHeader(decodedHeader, "Return-Receipt-To")) {
148+
parsedComponents.returnReceiptTo = createAddressFromEncodedHeader(header, "Return-Receipt-To");
149+
} else if (isEmailHeader(decodedHeader, "Return-Path")) {
150+
parsedComponents.bounceToAddress = createAddressFromEncodedHeader(header, "Return-Path");
152151
} else {
153-
if (!parsedComponents.headers.containsKey(headerName)) {
154-
parsedComponents.headers.put(headerName, new ArrayList<>());
152+
if (!parsedComponents.headers.containsKey(decodedHeader.getName())) {
153+
parsedComponents.headers.put(decodedHeader.getName(), new ArrayList<>());
155154
}
156-
parsedComponents.headers.get(headerName).add(MimeUtility.unfold(headerValue));
155+
parsedComponents.headers.get(decodedHeader.getName()).add(MimeUtility.unfold(decodedHeader.getValue()));
157156
}
158157
}
159158

@@ -280,25 +279,24 @@ private static String parseResourceName(@Nullable String possibleWrappedContentI
280279

281280
@SuppressWarnings("WeakerAccess")
282281
@NotNull
283-
public static List<DecodedHeader> retrieveAllHeaders(@NotNull final MimePart part) {
282+
public static List<Header> retrieveAllHeaders(@NotNull final MimePart part) {
284283
try {
285-
return Collections.list(part.getAllHeaders()).stream()
286-
.map(DecodedHeader::of)
287-
.collect(toList());
284+
return Collections.list(part.getAllHeaders());
288285
} catch (final MessagingException e) {
289286
throw new MimeMessageParseException(MimeMessageParseException.ERROR_GETTING_ALL_HEADERS, e);
290287
}
291288
}
292289

293290
@Nullable
294-
static InternetAddress createAddress(final String address, final String typeOfAddress) {
291+
static InternetAddress createAddressFromEncodedHeader(final Header headerWithAddress, final String typeOfAddress) {
292+
val encodedAddress = headerWithAddress.getValue();
295293
try {
296-
return address.trim().isEmpty() ? null : new InternetAddress(address);
294+
return encodedAddress.trim().isEmpty() ? null : InternetAddress.parseHeader(encodedAddress, true)[0];
297295
} catch (final AddressException e) {
298296
if (e.getMessage().equals("Empty address")) {
299297
return null;
300298
}
301-
throw new MimeMessageParseException(format(MimeMessageParseException.ERROR_PARSING_ADDRESS, typeOfAddress, address), e);
299+
throw new MimeMessageParseException(format(MimeMessageParseException.ERROR_PARSING_ADDRESS, typeOfAddress, encodedAddress), e);
302300
}
303301
}
304302

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ public void testProblematicEmbeddedImage() {
175175
assertThat(s1.getHTMLText()).containsPattern("\"cid:DB294AA3-160F-4825-923A-B16C8B674543@home\"");
176176
}
177177

178+
@Test
179+
public void testProblematicUmlautInDispositionNotificationTo() {
180+
Email s1 = EmailConverter.emlToEmail(new File(RESOURCE_TEST_MESSAGES + "/#500 Email with problematic umlaut in Disposition-Notification-To.eml"));
181+
EmailAssert.assertThat(s1).hasFromRecipient(new Recipient("Könok, Danny [Fake Company & Co. KG]", "[email protected]", null));
182+
EmailAssert.assertThat(s1).hasDispositionNotificationTo(new Recipient("Könok, Danny [Fake Company & Co. KG]", "[email protected]", null));
183+
}
184+
178185
@Test
179186
public void testProblematic8BitContentTransferEncoding() {
180187
Email s1 = EmailConverter.emlToEmail(new File(RESOURCE_TEST_MESSAGES + "/#485 Email with 8Bit Content Transfer Encoding.eml"));

modules/simple-java-mail/src/test/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageParserTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import jakarta.activation.DataHandler;
44
import jakarta.mail.BodyPart;
5+
import jakarta.mail.Header;
56
import jakarta.mail.MessagingException;
67
import jakarta.mail.Part;
78
import jakarta.mail.Session;
@@ -177,7 +178,7 @@ public void testCreateAddress() throws UnsupportedEncodingException {
177178

178179
@Nullable
179180
private InternetAddress interpretRecipient(String address) {
180-
return MimeMessageParser.createAddress(address, "TO");
181+
return MimeMessageParser.createAddressFromEncodedHeader(new Header("ignored", address), "TO");
181182
}
182183

183184
@Test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
From:
2+
=?iso-8859-1?Q?K=F6nok=2C_Danny_=5BFake_Company_=26_Co=2E_K?=
3+
=?iso-8859-1?Q?G=5D?= <[email protected]>
4+
To: "Bestellung [Fake Company]"
5+
6+
Subject: Test
7+
Disposition-Notification-To:
8+
=?iso-8859-1?Q?K=F6nok=2C_Danny_=5BFake_Company_=26_Co=2E_K?=
9+
=?iso-8859-1?Q?G=5D?= <[email protected]>
10+
Date: Thu, 28 Mar 2024 14:07:46 +0100
11+
Message-ID: <[email protected]>
12+
Accept-Language: de-DE, en-US
13+
Content-Language: de-DE
14+
Content-Type: text/html; charset="iso-8859-1"
15+
Content-Transfer-Encoding: quoted-printable
16+
MIME-Version: 1.0
17+
18+
<html xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:o=3D"urn:schemas-micr=
19+
osoft-com:office:office" xmlns:w=3D"urn:schemas-microsoft-com:office:word" =
20+
xmlns:m=3D"http://schemas.microsoft.com/office/2004/12/omml" xmlns=3D"http:=
21+
//www.w3.org/TR/REC-html40">
22+
<head>
23+
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Diso-8859-=
24+
1">
25+
<meta name=3D"Generator" content=3D"Microsoft Word 15 (filtered medium)">
26+
<style><!--
27+
/* Font Definitions */
28+
@font-face
29+
{font-family:"Cambria Math";
30+
panose-1:2 4 5 3 5 4 6 3 2 4;}
31+
@font-face
32+
{font-family:Calibri;
33+
panose-1:2 15 5 2 2 2 4 3 2 4;}
34+
/* Style Definitions */
35+
p.MsoNormal, li.MsoNormal, div.MsoNormal
36+
{margin:0cm;
37+
margin-bottom:.0001pt;
38+
font-size:11.0pt;
39+
font-family:"Calibri",sans-serif;
40+
mso-fareast-language:EN-US;}
41+
a:link, span.MsoHyperlink
42+
{mso-style-priority:99;
43+
color:#0563C1;
44+
text-decoration:underline;}
45+
a:visited, span.MsoHyperlinkFollowed
46+
{mso-style-priority:99;
47+
color:#954F72;
48+
text-decoration:underline;}
49+
span.E-MailFormatvorlage17
50+
{mso-style-type:personal-compose;
51+
font-family:"Calibri",sans-serif;
52+
color:windowtext;}
53+
..MsoChpDefault
54+
{mso-style-type:export-only;
55+
font-family:"Calibri",sans-serif;
56+
mso-fareast-language:EN-US;}
57+
@page WordSection1
58+
{size:612.0pt 792.0pt;
59+
margin:70.85pt 70.85pt 2.0cm 70.85pt;}
60+
div.WordSection1
61+
{page:WordSection1;}
62+
--></style><!--[if gte mso 9]><xml>
63+
<o:shapedefaults v:ext=3D"edit" spidmax=3D"1026" />
64+
</xml><![endif]--><!--[if gte mso 9]><xml>
65+
<o:shapelayout v:ext=3D"edit">
66+
<o:idmap v:ext=3D"edit" data=3D"1" />
67+
</o:shapelayout></xml><![endif]-->
68+
</head>
69+
<body lang=3D"DE" link=3D"#0563C1" vlink=3D"#954F72">
70+
<div class=3D"WordSection1">
71+
<p class=3D"MsoNormal"><span lang=3D"EN-US">Hallo,<o:p></o:p></span></p>
72+
<p class=3D"MsoNormal"><span lang=3D"EN-US"><o:p>&nbsp;</o:p></span></p>
73+
<p class=3D"MsoNormal"><span lang=3D"EN-US">Lirumlarum &#8211; 02.04.2=
74+
024<o:p></o:p></span></p>
75+
<p class=3D"MsoNormal"><span lang=3D"EN-US"><o:p>&nbsp;</o:p></span></p>
76+
<p class=3D"MsoNormal"><span lang=3D"EN-US">2,5t 550<o:p></o:p></span></p>
77+
<p class=3D"MsoNormal"><span lang=3D"EN-US">1,2t 997<o:p></o:p></span></p>
78+
<p class=3D"MsoNormal"><span lang=3D"EN-US"><o:p>&nbsp;</o:p></span></p>
79+
<p class=3D"MsoNormal"><span lang=3D"EN-US">Danke.<o:p></o:p></span></p>
80+
<p class=3D"MsoNormal"><span lang=3D"EN-US"><o:p>&nbsp;</o:p></span></p>
81+
<p class=3D"MsoNormal"><span lang=3D"EN-US">Gr=FC=DFe,<o:p></o:p></span></p=
82+
>
83+
<p class=3D"MsoNormal"><span lang=3D"EN-US"><o:p>&nbsp;</o:p></span></p>
84+
</div>
85+
<p style=3D"FONT-SIZE: 12pt; FONT-FAMILY: Calibri"><font style=3D"FONT-SIZE=
86+
: 12pt; FONT-FAMILY: Calibri"><span style=3D"FONT-SIZE: 12pt"><strong><span=
87+
style=3D"FONT-SIZE: 11pt"><span style=3D"FONT-SIZE: 12pt">Danny K=F6nok<=
88+
/span></span></strong></span></font></p>
89+
<table style=3D"FONT-SIZE: 11pt; FONT-FAMILY: Calibri">
90+
<tbody>
91+
<tr>
92+
<td colspan=3D"2">xxx/ Faktura</td>
93+
</tr>
94+
</tbody>
95+
<tbody>
96+
<tr>
97+
<td>Telefon:</td>
98+
<td>&#43;49 0000 / 000-000</td>
99+
</tr>
100+
<tr>
101+
<td></td>
102+
</tr>
103+
<tr>
104+
<td>Email:</td>
105+
106+
</tr>
107+
</tbody>
108+
</table>
109+
<br>
110+
</body>
111+
</html>
112+
.

0 commit comments

Comments
 (0)