Skip to content

Commit e943d37

Browse files
committed
#480: generate unique Content-ID's for attachments so attachments with identical names don't refer to the same content
1 parent e415b72 commit e943d37

File tree

4 files changed

+53
-36
lines changed

4 files changed

+53
-36
lines changed

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,8 @@ static void setAttachments(@NotNull final Email email, final MimeMultipart multi
205205
* Sets all headers on the {@link Message} instance. Since we're not using a high-level JavaMail method, the JavaMail library says we need to do
206206
* some encoding and 'folding' manually, to get the value right for the headers (see {@link MimeUtility}.
207207
* <p>
208-
* Furthermore sets the notification flags <code>Disposition-Notification-To</code> and <code>Return-Receipt-To</code> if provided. It used
209-
* JavaMail's built in method for producing an RFC compliant email address (see {@link InternetAddress#toString()}).
208+
* Furthermore, sets the notification flags <code>Disposition-Notification-To</code> and <code>Return-Receipt-To</code> if provided. It used
209+
* JavaMail's built-in method for producing an RFC compliant email address (see {@link InternetAddress#toString()}).
210210
*
211211
* @param email The message in which the headers are defined.
212212
* @param message The {@link Message} on which to set the raw, encoded and folded headers.
@@ -261,16 +261,16 @@ private static BodyPart getBodyPartFromDatasource(final AttachmentResource attac
261261
throws MessagingException {
262262
final BodyPart attachmentPart = new MimeBodyPart();
263263
// setting headers isn't working nicely using the javax mail API, so let's do that manually
264-
final String resourceName = determineResourceName(attachmentResource, dispositionType, true);
265-
final String fileName = determineResourceName(attachmentResource, dispositionType, false);
264+
final String fileName = determineResourceName(attachmentResource, dispositionType, false, false);
265+
final String contentID = determineResourceName(attachmentResource, dispositionType, true, true);
266266
attachmentPart.setDataHandler(new DataHandler(new NamedDataSource(fileName, attachmentResource.getDataSource())));
267267
attachmentPart.setFileName(fileName);
268268
final String contentType = attachmentResource.getDataSource().getContentType();
269269
ParameterList pl = new ParameterList();
270270
pl.set("filename", fileName);
271271
pl.set("name", fileName);
272272
attachmentPart.setHeader("Content-Type", contentType + pl);
273-
attachmentPart.setHeader("Content-ID", format("<%s>", resourceName));
273+
attachmentPart.setHeader("Content-ID", format("<%s>", contentID));
274274

275275
attachmentPart.setHeader("Content-Description", determineAttachmentDescription(attachmentResource));
276276
if (!valueNullOrEmpty(attachmentResource.getContentTransferEncoding())) {
@@ -283,7 +283,7 @@ private static BodyPart getBodyPartFromDatasource(final AttachmentResource attac
283283
/**
284284
* Determines the right resource name and optionally attaches the correct extension to the name. The result is mime encoded.
285285
*/
286-
static String determineResourceName(final AttachmentResource attachmentResource, String dispositionType, final boolean encodeResourceName) {
286+
static String determineResourceName(final AttachmentResource attachmentResource, String dispositionType, final boolean encodeResourceName, final boolean isContentID) {
287287
final String datasourceName = attachmentResource.getDataSource().getName();
288288

289289
String resourceName;
@@ -295,6 +295,13 @@ static String determineResourceName(final AttachmentResource attachmentResource,
295295
} else {
296296
resourceName = "resource" + UUID.randomUUID();
297297
}
298+
299+
// if ATTACHMENT, then add UUID to the name to prevent attachments with the same name to reference the same attachment content
300+
if (isContentID && dispositionType.equals(Part.ATTACHMENT)) {
301+
resourceName += "@" + UUID.randomUUID();
302+
}
303+
304+
// if there is no extension on the name, but there is on the datasource name, then add it to the name
298305
if (!valueNullOrEmpty(datasourceName) && dispositionType.equals(Part.ATTACHMENT)) {
299306
resourceName = possiblyAddExtension(datasourceName, resourceName);
300307
}

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

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,21 @@
1717
import java.io.IOException;
1818
import java.util.ArrayList;
1919
import java.util.List;
20+
import java.util.regex.Matcher;
2021

2122
import static demo.ResourceFolderHelper.determineResourceFolder;
2223
import static jakarta.mail.Message.RecipientType.CC;
2324
import static jakarta.mail.Message.RecipientType.TO;
25+
import static java.lang.String.format;
2426
import static java.nio.charset.Charset.defaultCharset;
2527
import static java.nio.charset.StandardCharsets.UTF_8;
2628
import static java.util.Collections.singletonList;
29+
import static java.util.regex.Pattern.compile;
2730
import static org.apache.commons.codec.binary.Base64.encodeBase64Chunked;
2831
import static org.assertj.core.api.Assertions.assertThat;
2932
import static org.simplejavamail.api.email.ContentTransferEncoding.BIT7;
3033
import static org.simplejavamail.internal.util.MiscUtil.normalizeNewlines;
34+
import static org.simplejavamail.internal.util.Preconditions.assumeTrue;
3135

3236
public class EmailConverterTest {
3337

@@ -205,10 +209,10 @@ public void testContentTransferEncodingBase64() {
205209
assertThat(emlRoundtrip).contains("Content-Transfer-Encoding: base64\n\n"
206210
+ asBase64("<b>We should meet up!</b><img src='cid:thumbsup'><img src='cid:fixedNameWithoutFileExtensionForNamedEmbeddedImage'>"));
207211

208-
assertThat(eml).contains("Content-ID: <dresscode.txt>");
209-
assertThat(eml).contains("Content-ID: <location.txt>");
212+
assertThat(eml).contains("Content-ID: <dresscode.txt@" + contentIDExtractor(eml, "dresscode.txt") + ">");
213+
assertThat(eml).contains("Content-ID: <location.txt@" + contentIDExtractor(eml, "location.txt") + ">");
210214
assertThat(eml).contains("Content-ID: <thumbsup>");
211-
assertThat(eml).contains("Content-ID: <fixedNameWithoutFileExtensionForNamedAttachment.txt>");
215+
assertThat(eml).contains("Content-ID: <fixedNameWithoutFileExtensionForNamedAttachment@" + contentIDExtractor(eml, "fixedNameWithoutFileExtensionForNamedAttachment") + ".txt>");
212216
assertThat(eml).contains("Content-ID: <fixedNameWithoutFileExtensionForNamedEmbeddedImage>");
213217
}
214218

@@ -239,28 +243,28 @@ public void testContentDescriptionAndContentTransferEncoding() throws IOExceptio
239243
assertThat(eml).contains("Content-Type: text/plain; filename=\"dummy text1.txt\"; name=\"dummy text1.txt\"\n"
240244
+ "Content-Transfer-Encoding: 7bit\n"
241245
+ "Content-Disposition: attachment; filename=\"dummy text1.txt\"\n"
242-
+ "Content-ID: <dummy text1.txt>\n"
246+
+ "Content-ID: <dummy text1.txt@" + contentIDExtractor(eml, "dummy text1.txt") + ">\n"
243247
+ "Content-Description: This is dummy text1\n"
244248
+ "\n"
245249
+ "Cupcake ipsum dolor sit amet donut. Apple pie caramels oat cake fruitcake sesame snaps. Bear claw cotton candy toffee danish sweet roll.");
246250
assertThat(eml).contains("Content-Type: text/plain; filename=\"dummy text2.txt\"; name=\"dummy text2.txt\"\n"
247251
+ "Content-Transfer-Encoding: 7bit\n"
248252
+ "Content-Disposition: attachment; filename=\"dummy text2.txt\"\n"
249-
+ "Content-ID: <dummy text2.txt>\n"
253+
+ "Content-ID: <dummy text2.txt@" + contentIDExtractor(eml, "dummy text2.txt") + ">\n"
250254
+ "Content-Description: This is dummy text2\n"
251255
+ "\n"
252256
+ "I love pie I love donut sugar plum. I love halvah topping bonbon fruitcake brownie chocolate. Sweet tootsie roll wafer caramels sesame snaps.");
253257
assertThat(eml).contains("Content-Type: text/plain; filename=\"dummy text3.txt\"; name=\"dummy text3.txt\"\n"
254258
+ "Content-Transfer-Encoding: 7bit\n"
255259
+ "Content-Disposition: attachment; filename=\"dummy text3.txt\"\n"
256-
+ "Content-ID: <dummy text3.txt>\n"
260+
+ "Content-ID: <dummy text3.txt@" + contentIDExtractor(eml, "dummy text3.txt") + ">\n"
257261
+ "Content-Description: This is dummy text3\n"
258262
+ "\n"
259263
+ "Danish chocolate pudding cake bonbon powder bonbon. I love cookie jelly beans cake oat cake. I love I love sweet roll sweet pudding topping icing.");
260264
assertThat(eml).contains("Content-Type: text/plain; filename=\"dummy text4.txt\"; name=\"dummy text4.txt\"\n"
261265
+ "Content-Transfer-Encoding: 7bit\n"
262266
+ "Content-Disposition: attachment; filename=\"dummy text4.txt\"\n"
263-
+ "Content-ID: <dummy text4.txt>\n"
267+
+ "Content-ID: <dummy text4.txt@" + contentIDExtractor(eml, "dummy text4.txt") + ">\n"
264268
+ "\n"
265269
+ "this should not have a Content-Description header");
266270

@@ -269,32 +273,38 @@ public void testContentDescriptionAndContentTransferEncoding() throws IOExceptio
269273
assertThat(emlRoundtrip).contains("Content-Type: text/plain; filename=\"dummy text1.txt\"; name=\"dummy text1.txt\"\n"
270274
+ "Content-Transfer-Encoding: 7bit\n"
271275
+ "Content-Disposition: attachment; filename=\"dummy text1.txt\"\n"
272-
+ "Content-ID: <dummy text1.txt>\n"
276+
+ "Content-ID: <dummy text1.txt@" + contentIDExtractor(emlRoundtrip, "dummy text1.txt") + ">\n"
273277
+ "Content-Description: This is dummy text1\n"
274278
+ "\n"
275279
+ "Cupcake ipsum dolor sit amet donut. Apple pie caramels oat cake fruitcake sesame snaps. Bear claw cotton candy toffee danish sweet roll.");
276280
assertThat(emlRoundtrip).contains("Content-Type: text/plain; filename=\"dummy text2.txt\"; name=\"dummy text2.txt\"\n"
277281
+ "Content-Transfer-Encoding: 7bit\n"
278282
+ "Content-Disposition: attachment; filename=\"dummy text2.txt\"\n"
279-
+ "Content-ID: <dummy text2.txt>\n"
283+
+ "Content-ID: <dummy text2.txt@" + contentIDExtractor(emlRoundtrip, "dummy text2.txt") + ">\n"
280284
+ "Content-Description: This is dummy text2\n"
281285
+ "\n"
282286
+ "I love pie I love donut sugar plum. I love halvah topping bonbon fruitcake brownie chocolate. Sweet tootsie roll wafer caramels sesame snaps.");
283287
assertThat(emlRoundtrip).contains("Content-Type: text/plain; filename=\"dummy text3.txt\"; name=\"dummy text3.txt\"\n"
284288
+ "Content-Transfer-Encoding: 7bit\n"
285289
+ "Content-Disposition: attachment; filename=\"dummy text3.txt\"\n"
286-
+ "Content-ID: <dummy text3.txt>\n"
290+
+ "Content-ID: <dummy text3.txt@" + contentIDExtractor(emlRoundtrip, "dummy text3.txt") + ">\n"
287291
+ "Content-Description: This is dummy text3\n"
288292
+ "\n"
289293
+ "Danish chocolate pudding cake bonbon powder bonbon. I love cookie jelly beans cake oat cake. I love I love sweet roll sweet pudding topping icing.");
290294
assertThat(emlRoundtrip).contains("Content-Type: text/plain; filename=\"dummy text4.txt\"; name=\"dummy text4.txt\"\n"
291295
+ "Content-Transfer-Encoding: 7bit\n"
292296
+ "Content-Disposition: attachment; filename=\"dummy text4.txt\"\n"
293-
+ "Content-ID: <dummy text4.txt>\n"
297+
+ "Content-ID: <dummy text4.txt@" + contentIDExtractor(emlRoundtrip, "dummy text4.txt") + ">\n"
294298
+ "\n"
295299
+ "this should not have a Content-Description header");
296300
}
297301

302+
private static String contentIDExtractor(String eml, String filename) {
303+
final Matcher matcher = compile(format("Content-ID: <%s@(?<uuid>.+?)(?<optionalExtension>\\..{3})?>", filename)).matcher(eml);
304+
assertThat(matcher.find()).as(format("Found UUID in EML's Content-ID for filename '%s'", filename)).isTrue();
305+
return matcher.group("uuid");
306+
}
307+
298308
@NotNull
299309
private List<AttachmentResource> asList(AttachmentResource attachment) {
300310
List<AttachmentResource> collectionAttachment = new ArrayList<>();

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

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,64 +35,64 @@ public void setup() {
3535
public void determineResourceName1()
3636
throws IOException {
3737
AttachmentResource resource1 = new AttachmentResource(null, getDataSource("blahblah"));
38-
assertThat(MimeMessageHelper.determineResourceName(resource1, Part.ATTACHMENT, true)).isEqualTo("blahblah");
39-
assertThat(MimeMessageHelper.determineResourceName(resource1, Part.INLINE, true)).isEqualTo("blahblah");
38+
assertThat(MimeMessageHelper.determineResourceName(resource1, Part.ATTACHMENT, true, false)).isEqualTo("blahblah");
39+
assertThat(MimeMessageHelper.determineResourceName(resource1, Part.INLINE, true, false)).isEqualTo("blahblah");
4040
}
4141

4242
@Test
4343
public void determineResourceName2()
4444
throws IOException {
4545
AttachmentResource resource2 = new AttachmentResource(null, getDataSource("blahblah.txt"));
46-
assertThat(MimeMessageHelper.determineResourceName(resource2, Part.ATTACHMENT, true)).isEqualTo("blahblah.txt");
47-
assertThat(MimeMessageHelper.determineResourceName(resource2, Part.INLINE, true)).isEqualTo("blahblah.txt");
46+
assertThat(MimeMessageHelper.determineResourceName(resource2, Part.ATTACHMENT, true, false)).isEqualTo("blahblah.txt");
47+
assertThat(MimeMessageHelper.determineResourceName(resource2, Part.INLINE, true, false)).isEqualTo("blahblah.txt");
4848
}
4949

5050
@Test
5151
public void determineResourceName3()
5252
throws IOException {
5353
AttachmentResource resource3 = new AttachmentResource("the resource", getDataSource(null));
54-
assertThat(MimeMessageHelper.determineResourceName(resource3, Part.ATTACHMENT, true)).isEqualTo("the resource");
55-
assertThat(MimeMessageHelper.determineResourceName(resource3, Part.INLINE, true)).isEqualTo("the resource");
54+
assertThat(MimeMessageHelper.determineResourceName(resource3, Part.ATTACHMENT, true, false)).isEqualTo("the resource");
55+
assertThat(MimeMessageHelper.determineResourceName(resource3, Part.INLINE, true, false)).isEqualTo("the resource");
5656
}
5757

5858
@Test
5959
public void determineResourceName4()
6060
throws IOException {
6161
AttachmentResource resource4 = new AttachmentResource("the resource", getDataSource("blahblah.txt"));
62-
assertThat(MimeMessageHelper.determineResourceName(resource4, Part.ATTACHMENT, true)).isEqualTo("the resource.txt");
63-
assertThat(MimeMessageHelper.determineResourceName(resource4, Part.INLINE, true)).isEqualTo("the resource");
62+
assertThat(MimeMessageHelper.determineResourceName(resource4, Part.ATTACHMENT, true, false)).isEqualTo("the resource.txt");
63+
assertThat(MimeMessageHelper.determineResourceName(resource4, Part.INLINE, true, false)).isEqualTo("the resource");
6464
}
6565

6666
@Test
6767
public void determineResourceName5()
6868
throws IOException {
6969
AttachmentResource resource5 = new AttachmentResource("the resource", getDataSource("blahblah"));
70-
assertThat(MimeMessageHelper.determineResourceName(resource5, Part.ATTACHMENT, true)).isEqualTo("the resource");
71-
assertThat(MimeMessageHelper.determineResourceName(resource5, Part.INLINE, true)).isEqualTo("the resource");
70+
assertThat(MimeMessageHelper.determineResourceName(resource5, Part.ATTACHMENT, true, false)).isEqualTo("the resource");
71+
assertThat(MimeMessageHelper.determineResourceName(resource5, Part.INLINE, true, false)).isEqualTo("the resource");
7272
}
7373

7474
@Test
7575
public void determineResourceName6()
7676
throws IOException {
7777
AttachmentResource resource6 = new AttachmentResource("the resource.txt", getDataSource("blahblah.txt"));
78-
assertThat(MimeMessageHelper.determineResourceName(resource6, Part.ATTACHMENT, true)).isEqualTo("the resource.txt");
79-
assertThat(MimeMessageHelper.determineResourceName(resource6, Part.INLINE, true)).isEqualTo("the resource.txt");
78+
assertThat(MimeMessageHelper.determineResourceName(resource6, Part.ATTACHMENT, true, false)).isEqualTo("the resource.txt");
79+
assertThat(MimeMessageHelper.determineResourceName(resource6, Part.INLINE, true, false)).isEqualTo("the resource.txt");
8080
}
8181

8282
@Test
8383
public void determineResourceName7()
8484
throws IOException {
8585
AttachmentResource resource7 = new AttachmentResource("the resource.txt", getDataSource("blahblah"));
86-
assertThat(MimeMessageHelper.determineResourceName(resource7, Part.ATTACHMENT, true)).isEqualTo("the resource.txt");
87-
assertThat(MimeMessageHelper.determineResourceName(resource7, Part.INLINE, true)).isEqualTo("the resource.txt");
86+
assertThat(MimeMessageHelper.determineResourceName(resource7, Part.ATTACHMENT, true, false)).isEqualTo("the resource.txt");
87+
assertThat(MimeMessageHelper.determineResourceName(resource7, Part.INLINE, true, false)).isEqualTo("the resource.txt");
8888
}
8989

9090
@Test
9191
public void determineResourceName_ignoreExtensionFromResource()
9292
throws IOException {
9393
AttachmentResource resource7 = new AttachmentResource("the resource.txt", getDataSource("blahblah.1/www/get?id=3"));
94-
assertThat(MimeMessageHelper.determineResourceName(resource7, Part.ATTACHMENT, true)).isEqualTo("the resource.txt");
95-
assertThat(MimeMessageHelper.determineResourceName(resource7, Part.INLINE, true)).isEqualTo("the resource.txt");
94+
assertThat(MimeMessageHelper.determineResourceName(resource7, Part.ATTACHMENT, true, false)).isEqualTo("the resource.txt");
95+
assertThat(MimeMessageHelper.determineResourceName(resource7, Part.INLINE, true, false)).isEqualTo("the resource.txt");
9696
}
9797

9898
private ByteArrayDataSource getDataSource(@Nullable String name)

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -556,9 +556,9 @@ public void createMailSession_ReplyToMessage_NotAll_AndCustomReferences()
556556
assertThat(receivedReplyToReply.getHeaders()).contains(entry("References", singletonList(references)));
557557
}
558558

559-
private void assertAttachmentMetadata(AttachmentResource embeddedImg, String mimeType, String filename) {
560-
assertThat(embeddedImg.getDataSource().getContentType()).isEqualTo(mimeType);
561-
assertThat(embeddedImg.getName()).isEqualTo(filename);
559+
private void assertAttachmentMetadata(AttachmentResource attachment, String mimeType, String filename) {
560+
assertThat(attachment.getDataSource().getContentType()).isEqualTo(mimeType);
561+
assertThat(attachment.getName()).isEqualTo(filename);
562562
}
563563

564564
@Test

0 commit comments

Comments
 (0)