Skip to content

Commit

Permalink
#500: [bug] Fix parsing addresses from headers in EML files, like a D…
Browse files Browse the repository at this point in the history
…isposition-Notification-To with umlaut
  • Loading branch information
bbottema committed Apr 4, 2024
1 parent c752fe9 commit a0f57e2
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.simplejavamail.internal.config;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
Expand All @@ -14,6 +15,7 @@
*/
@RequiredArgsConstructor
@Getter
@SuppressFBWarnings("SE_BAD_FIELD")
public enum EmailProperty {

HEADERS(Email::getHeaders, true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.activation.*;
import jakarta.mail.Address;
import jakarta.mail.Header;
import jakarta.mail.Message.RecipientType;
import jakarta.mail.MessagingException;
import jakarta.mail.Multipart;
Expand Down Expand Up @@ -29,7 +30,6 @@
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static org.simplejavamail.internal.util.MiscUtil.extractCID;
import static org.simplejavamail.internal.util.MiscUtil.valueNullOrEmpty;
import static org.slf4j.LoggerFactory.getLogger;
Expand Down Expand Up @@ -75,7 +75,7 @@ public static ParsedMimeMessageComponents parseMimeMessage(@NotNull final MimeMe
}

private static void parseMimePartTree(@NotNull final MimePart currentPart, @NotNull final ParsedMimeMessageComponents parsedComponents, final boolean fetchAttachmentData) {
for (final DecodedHeader header : retrieveAllHeaders(currentPart)) {
for (final Header header : retrieveAllHeaders(currentPart)) {
parseHeader(header, parsedComponents);
}

Expand Down Expand Up @@ -122,8 +122,8 @@ private static void parseDataSource(@NotNull MimePart currentPart, @NotNull Pars

private static void checkContentTransferEncoding(final MimePart currentPart, @NotNull final ParsedMimeMessageComponents parsedComponents) {
if (parsedComponents.contentTransferEncoding == null) {
for (final DecodedHeader header : retrieveAllHeaders(currentPart)) {
if (isEmailHeader(header, "Content-Transfer-Encoding")) {
for (final Header header : retrieveAllHeaders(currentPart)) {
if (isEmailHeader(DecodedHeader.of(header), "Content-Transfer-Encoding")) {
parsedComponents.contentTransferEncoding = header.getValue();
}
}
Expand All @@ -139,21 +139,20 @@ private static MimeDataSource parseAttachment(@Nullable final String contentId,
.build();
}

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

if (isEmailHeader(header, "Disposition-Notification-To")) {
parsedComponents.dispositionNotificationTo = createAddress(headerValue, "Disposition-Notification-To");
} else if (isEmailHeader(header, "Return-Receipt-To")) {
parsedComponents.returnReceiptTo = createAddress(headerValue, "Return-Receipt-To");
} else if (isEmailHeader(header, "Return-Path")) {
parsedComponents.bounceToAddress = createAddress(headerValue, "Return-Path");
if (isEmailHeader(decodedHeader, "Disposition-Notification-To")) {
parsedComponents.dispositionNotificationTo = createAddressFromEncodedHeader(header, "Disposition-Notification-To");
} else if (isEmailHeader(decodedHeader, "Return-Receipt-To")) {
parsedComponents.returnReceiptTo = createAddressFromEncodedHeader(header, "Return-Receipt-To");
} else if (isEmailHeader(decodedHeader, "Return-Path")) {
parsedComponents.bounceToAddress = createAddressFromEncodedHeader(header, "Return-Path");
} else {
if (!parsedComponents.headers.containsKey(headerName)) {
parsedComponents.headers.put(headerName, new ArrayList<>());
if (!parsedComponents.headers.containsKey(decodedHeader.getName())) {
parsedComponents.headers.put(decodedHeader.getName(), new ArrayList<>());
}
parsedComponents.headers.get(headerName).add(MimeUtility.unfold(headerValue));
parsedComponents.headers.get(decodedHeader.getName()).add(MimeUtility.unfold(decodedHeader.getValue()));
}
}

Expand Down Expand Up @@ -280,25 +279,24 @@ private static String parseResourceName(@Nullable String possibleWrappedContentI

@SuppressWarnings("WeakerAccess")
@NotNull
public static List<DecodedHeader> retrieveAllHeaders(@NotNull final MimePart part) {
public static List<Header> retrieveAllHeaders(@NotNull final MimePart part) {
try {
return Collections.list(part.getAllHeaders()).stream()
.map(DecodedHeader::of)
.collect(toList());
return Collections.list(part.getAllHeaders());
} catch (final MessagingException e) {
throw new MimeMessageParseException(MimeMessageParseException.ERROR_GETTING_ALL_HEADERS, e);
}
}

@Nullable
static InternetAddress createAddress(final String address, final String typeOfAddress) {
static InternetAddress createAddressFromEncodedHeader(final Header headerWithAddress, final String typeOfAddress) {
val encodedAddress = headerWithAddress.getValue();
try {
return address.trim().isEmpty() ? null : new InternetAddress(address);
return encodedAddress.trim().isEmpty() ? null : InternetAddress.parseHeader(encodedAddress, true)[0];
} catch (final AddressException e) {
if (e.getMessage().equals("Empty address")) {
return null;
}
throw new MimeMessageParseException(format(MimeMessageParseException.ERROR_PARSING_ADDRESS, typeOfAddress, address), e);
throw new MimeMessageParseException(format(MimeMessageParseException.ERROR_PARSING_ADDRESS, typeOfAddress, encodedAddress), e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,13 @@ public void testProblematicEmbeddedImage() {
assertThat(s1.getHTMLText()).containsPattern("\"cid:DB294AA3-160F-4825-923A-B16C8B674543@home\"");
}

@Test
public void testProblematicUmlautInDispositionNotificationTo() {
Email s1 = EmailConverter.emlToEmail(new File(RESOURCE_TEST_MESSAGES + "/#500 Email with problematic umlaut in Disposition-Notification-To.eml"));
EmailAssert.assertThat(s1).hasFromRecipient(new Recipient("Könok, Danny [Fake Company & Co. KG]", "[email protected]", null));
EmailAssert.assertThat(s1).hasDispositionNotificationTo(new Recipient("Könok, Danny [Fake Company & Co. KG]", "[email protected]", null));
}

@Test
public void testProblematic8BitContentTransferEncoding() {
Email s1 = EmailConverter.emlToEmail(new File(RESOURCE_TEST_MESSAGES + "/#485 Email with 8Bit Content Transfer Encoding.eml"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.activation.DataHandler;
import jakarta.mail.BodyPart;
import jakarta.mail.Header;
import jakarta.mail.MessagingException;
import jakarta.mail.Part;
import jakarta.mail.Session;
Expand Down Expand Up @@ -177,7 +178,7 @@ public void testCreateAddress() throws UnsupportedEncodingException {

@Nullable
private InternetAddress interpretRecipient(String address) {
return MimeMessageParser.createAddress(address, "TO");
return MimeMessageParser.createAddressFromEncodedHeader(new Header("ignored", address), "TO");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
From:
=?iso-8859-1?Q?K=F6nok=2C_Danny_=5BFake_Company_=26_Co=2E_K?=
=?iso-8859-1?Q?G=5D?= <[email protected]>
To: "Bestellung [Fake Company]"
<[email protected]>
Subject: Test
Disposition-Notification-To:
=?iso-8859-1?Q?K=F6nok=2C_Danny_=5BFake_Company_=26_Co=2E_K?=
=?iso-8859-1?Q?G=5D?= <[email protected]>
Date: Thu, 28 Mar 2024 14:07:46 +0100
Message-ID: <[email protected]>
Accept-Language: de-DE, en-US
Content-Language: de-DE
Content-Type: text/html; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
MIME-Version: 1.0

<html xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:o=3D"urn:schemas-micr=
osoft-com:office:office" xmlns:w=3D"urn:schemas-microsoft-com:office:word" =
xmlns:m=3D"http://schemas.microsoft.com/office/2004/12/omml" xmlns=3D"http:=
//www.w3.org/TR/REC-html40">
<head>
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Diso-8859-=
1">
<meta name=3D"Generator" content=3D"Microsoft Word 15 (filtered medium)">
<style><!--
/* Font Definitions */
@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;}
@font-face
{font-family:Calibri;
panose-1:2 15 5 2 2 2 4 3 2 4;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{margin:0cm;
margin-bottom:.0001pt;
font-size:11.0pt;
font-family:"Calibri",sans-serif;
mso-fareast-language:EN-US;}
a:link, span.MsoHyperlink
{mso-style-priority:99;
color:#0563C1;
text-decoration:underline;}
a:visited, span.MsoHyperlinkFollowed
{mso-style-priority:99;
color:#954F72;
text-decoration:underline;}
span.E-MailFormatvorlage17
{mso-style-type:personal-compose;
font-family:"Calibri",sans-serif;
color:windowtext;}
..MsoChpDefault
{mso-style-type:export-only;
font-family:"Calibri",sans-serif;
mso-fareast-language:EN-US;}
@page WordSection1
{size:612.0pt 792.0pt;
margin:70.85pt 70.85pt 2.0cm 70.85pt;}
div.WordSection1
{page:WordSection1;}
--></style><!--[if gte mso 9]><xml>
<o:shapedefaults v:ext=3D"edit" spidmax=3D"1026" />
</xml><![endif]--><!--[if gte mso 9]><xml>
<o:shapelayout v:ext=3D"edit">
<o:idmap v:ext=3D"edit" data=3D"1" />
</o:shapelayout></xml><![endif]-->
</head>
<body lang=3D"DE" link=3D"#0563C1" vlink=3D"#954F72">
<div class=3D"WordSection1">
<p class=3D"MsoNormal"><span lang=3D"EN-US">Hallo,<o:p></o:p></span></p>
<p class=3D"MsoNormal"><span lang=3D"EN-US"><o:p>&nbsp;</o:p></span></p>
<p class=3D"MsoNormal"><span lang=3D"EN-US">Lirumlarum &#8211; 02.04.2=
024<o:p></o:p></span></p>
<p class=3D"MsoNormal"><span lang=3D"EN-US"><o:p>&nbsp;</o:p></span></p>
<p class=3D"MsoNormal"><span lang=3D"EN-US">2,5t 550<o:p></o:p></span></p>
<p class=3D"MsoNormal"><span lang=3D"EN-US">1,2t 997<o:p></o:p></span></p>
<p class=3D"MsoNormal"><span lang=3D"EN-US"><o:p>&nbsp;</o:p></span></p>
<p class=3D"MsoNormal"><span lang=3D"EN-US">Danke.<o:p></o:p></span></p>
<p class=3D"MsoNormal"><span lang=3D"EN-US"><o:p>&nbsp;</o:p></span></p>
<p class=3D"MsoNormal"><span lang=3D"EN-US">Gr=FC=DFe,<o:p></o:p></span></p=
>
<p class=3D"MsoNormal"><span lang=3D"EN-US"><o:p>&nbsp;</o:p></span></p>
</div>
<p style=3D"FONT-SIZE: 12pt; FONT-FAMILY: Calibri"><font style=3D"FONT-SIZE=
: 12pt; FONT-FAMILY: Calibri"><span style=3D"FONT-SIZE: 12pt"><strong><span=
style=3D"FONT-SIZE: 11pt"><span style=3D"FONT-SIZE: 12pt">Danny K=F6nok<=
/span></span></strong></span></font></p>
<table style=3D"FONT-SIZE: 11pt; FONT-FAMILY: Calibri">
<tbody>
<tr>
<td colspan=3D"2">xxx/ Faktura</td>
</tr>
</tbody>
<tbody>
<tr>
<td>Telefon:</td>
<td>&#43;49 0000 / 000-000</td>
</tr>
<tr>
<td></td>
</tr>
<tr>
<td>Email:</td>
<td>[email protected]</td>
</tr>
</tbody>
</table>
<br>
</body>
</html>
.

0 comments on commit a0f57e2

Please sign in to comment.