Skip to content

Commit bad2b90

Browse files
committed
MimeMessageHelper encodes attachment filename if not ASCII compliant
Issue: SPR-9258
1 parent 9a04191 commit bad2b90

File tree

1 file changed

+59
-53
lines changed

1 file changed

+59
-53
lines changed

org.springframework.context.support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java

Lines changed: 59 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@
3535
import javax.mail.internet.MimeMessage;
3636
import javax.mail.internet.MimeMultipart;
3737
import javax.mail.internet.MimePart;
38+
import javax.mail.internet.MimeUtility;
3839

3940
import org.springframework.core.io.InputStreamSource;
4041
import org.springframework.core.io.Resource;
@@ -241,7 +242,7 @@ public MimeMessageHelper(MimeMessage mimeMessage, boolean multipart) throws Mess
241242
* @see #MimeMessageHelper(javax.mail.internet.MimeMessage, int, String)
242243
*/
243244
public MimeMessageHelper(MimeMessage mimeMessage, boolean multipart, String encoding)
244-
throws MessagingException {
245+
throws MessagingException {
245246

246247
this(mimeMessage, (multipart ? MULTIPART_MODE_MIXED_RELATED : MULTIPART_MODE_NO), encoding);
247248
}
@@ -283,7 +284,7 @@ public MimeMessageHelper(MimeMessage mimeMessage, int multipartMode) throws Mess
283284
* @see #MULTIPART_MODE_MIXED_RELATED
284285
*/
285286
public MimeMessageHelper(MimeMessage mimeMessage, int multipartMode, String encoding)
286-
throws MessagingException {
287+
throws MessagingException {
287288

288289
this.mimeMessage = mimeMessage;
289290
createMimeMultiparts(mimeMessage, multipartMode);
@@ -355,7 +356,7 @@ protected void createMimeMultiparts(MimeMessage mimeMessage, int multipartMode)
355356
/**
356357
* Set the given MimeMultipart objects for use by this MimeMessageHelper.
357358
* @param root the root MimeMultipart object, which attachments will be added to;
358-
* or <code>null</code> to indicate no multipart at all
359+
* or {@code null} to indicate no multipart at all
359360
* @param main the main MimeMultipart object, which text(s) and inline elements
360361
* will be added to (can be the same as the root multipart object, or an element
361362
* nested underneath the root multipart element)
@@ -380,8 +381,8 @@ public final boolean isMultipart() {
380381
private void checkMultipart() throws IllegalStateException {
381382
if (!isMultipart()) {
382383
throw new IllegalStateException("Not in multipart mode - " +
383-
"create an appropriate MimeMessageHelper via a constructor that takes a 'multipart' flag " +
384-
"if you need to set alternative texts or add inline elements or attachments.");
384+
"create an appropriate MimeMessageHelper via a constructor that takes a 'multipart' flag " +
385+
"if you need to set alternative texts or add inline elements or attachments.");
385386
}
386387
}
387388

@@ -420,7 +421,7 @@ public final MimeMultipart getMimeMultipart() throws IllegalStateException {
420421
* Determine the default encoding for the given MimeMessage.
421422
* @param mimeMessage the passed-in MimeMessage
422423
* @return the default encoding associated with the MimeMessage,
423-
* or <code>null</code> if none found
424+
* or {@code null} if none found
424425
*/
425426
protected String getDefaultEncoding(MimeMessage mimeMessage) {
426427
if (mimeMessage instanceof SmartMimeMessage) {
@@ -456,12 +457,12 @@ protected FileTypeMap getDefaultFileTypeMap(MimeMessage mimeMessage) {
456457
}
457458

458459
/**
459-
* Set the Java Activation Framework <code>FileTypeMap</code> to use
460+
* Set the Java Activation Framework {@code FileTypeMap} to use
460461
* for determining the content type of inline content and attachments
461462
* that get added to the message.
462-
* <p>Default is the <code>FileTypeMap</code> that the underlying
463+
* <p>Default is the {@code FileTypeMap} that the underlying
463464
* MimeMessage carries, if any, or the Activation Framework's default
464-
* <code>FileTypeMap</code> instance else.
465+
* {@code FileTypeMap} instance else.
465466
* @see #addInline
466467
* @see #addAttachment
467468
* @see #getDefaultFileTypeMap(javax.mail.internet.MimeMessage)
@@ -474,7 +475,7 @@ public void setFileTypeMap(FileTypeMap fileTypeMap) {
474475
}
475476

476477
/**
477-
* Return the <code>FileTypeMap</code> used by this MimeMessageHelper.
478+
* Return the {@code FileTypeMap} used by this MimeMessageHelper.
478479
*/
479480
public FileTypeMap getFileTypeMap() {
480481
return this.fileTypeMap;
@@ -485,7 +486,7 @@ public FileTypeMap getFileTypeMap() {
485486
* Set whether to validate all addresses which get passed to this helper.
486487
* Default is "false".
487488
* <p>Note that this is by default just available for JavaMail >= 1.3.
488-
* You can override the default <code>validateAddress method</code> for
489+
* You can override the default {@code validateAddress method} for
489490
* validation on older JavaMail versions (or for custom validation).
490491
* @see #validateAddress
491492
*/
@@ -503,7 +504,7 @@ public boolean isValidateAddresses() {
503504
/**
504505
* Validate the given mail address.
505506
* Called by all of MimeMessageHelper's address setters and adders.
506-
* <p>Default implementation invokes <code>InternetAddress.validate()</code>,
507+
* <p>Default implementation invokes {@code InternetAddress.validate()},
507508
* provided that address validation is activated for the helper instance.
508509
* <p>Note that this method will just work on JavaMail >= 1.3. You can override
509510
* it for validation on older JavaMail versions or for custom validation.
@@ -546,7 +547,7 @@ public void setFrom(String from) throws MessagingException {
546547
public void setFrom(String from, String personal) throws MessagingException, UnsupportedEncodingException {
547548
Assert.notNull(from, "From address must not be null");
548549
setFrom(getEncoding() != null ?
549-
new InternetAddress(from, personal, getEncoding()) : new InternetAddress(from, personal));
550+
new InternetAddress(from, personal, getEncoding()) : new InternetAddress(from, personal));
550551
}
551552

552553
public void setReplyTo(InternetAddress replyTo) throws MessagingException {
@@ -608,8 +609,8 @@ public void addTo(String to) throws MessagingException {
608609
public void addTo(String to, String personal) throws MessagingException, UnsupportedEncodingException {
609610
Assert.notNull(to, "To address must not be null");
610611
addTo(getEncoding() != null ?
611-
new InternetAddress(to, personal, getEncoding()) :
612-
new InternetAddress(to, personal));
612+
new InternetAddress(to, personal, getEncoding()) :
613+
new InternetAddress(to, personal));
613614
}
614615

615616

@@ -653,8 +654,8 @@ public void addCc(String cc) throws MessagingException {
653654
public void addCc(String cc, String personal) throws MessagingException, UnsupportedEncodingException {
654655
Assert.notNull(cc, "Cc address must not be null");
655656
addCc(getEncoding() != null ?
656-
new InternetAddress(cc, personal, getEncoding()) :
657-
new InternetAddress(cc, personal));
657+
new InternetAddress(cc, personal, getEncoding()) :
658+
new InternetAddress(cc, personal));
658659
}
659660

660661

@@ -698,8 +699,8 @@ public void addBcc(String bcc) throws MessagingException {
698699
public void addBcc(String bcc, String personal) throws MessagingException, UnsupportedEncodingException {
699700
Assert.notNull(bcc, "Bcc address must not be null");
700701
addBcc(getEncoding() != null ?
701-
new InternetAddress(bcc, personal, getEncoding()) :
702-
new InternetAddress(bcc, personal));
702+
new InternetAddress(bcc, personal, getEncoding()) :
703+
new InternetAddress(bcc, personal));
703704
}
704705

705706
private InternetAddress parseAddress(String address) throws MessagingException {
@@ -730,7 +731,7 @@ public void setPriority(int priority) throws MessagingException {
730731

731732
/**
732733
* Set the sent-date of the message.
733-
* @param sentDate the date to set (never <code>null</code>)
734+
* @param sentDate the date to set (never {@code null})
734735
* @throws MessagingException in case of errors
735736
*/
736737
public void setSentDate(Date sentDate) throws MessagingException {
@@ -758,7 +759,7 @@ public void setSubject(String subject) throws MessagingException {
758759
* Set the given text directly as content in non-multipart mode
759760
* or as default body part in multipart mode.
760761
* Always applies the default content type "text/plain".
761-
* <p><b>NOTE:</b> Invoke {@link #addInline} <i>after</i> <code>setText</code>;
762+
* <p><b>NOTE:</b> Invoke {@link #addInline} <i>after</i> {@code setText};
762763
* else, mail readers might not be able to resolve inline references correctly.
763764
* @param text the text for the message
764765
* @throws MessagingException in case of errors
@@ -771,7 +772,7 @@ public void setText(String text) throws MessagingException {
771772
* Set the given text directly as content in non-multipart mode
772773
* or as default body part in multipart mode.
773774
* The "html" flag determines the content type to apply.
774-
* <p><b>NOTE:</b> Invoke {@link #addInline} <i>after</i> <code>setText</code>;
775+
* <p><b>NOTE:</b> Invoke {@link #addInline} <i>after</i> {@code setText};
775776
* else, mail readers might not be able to resolve inline references correctly.
776777
* @param text the text for the message
777778
* @param html whether to apply content type "text/html" for an
@@ -798,7 +799,7 @@ public void setText(String text, boolean html) throws MessagingException {
798799
/**
799800
* Set the given plain text and HTML text as alternatives, offering
800801
* both options to the email client. Requires multipart mode.
801-
* <p><b>NOTE:</b> Invoke {@link #addInline} <i>after</i> <code>setText</code>;
802+
* <p><b>NOTE:</b> Invoke {@link #addInline} <i>after</i> {@code setText};
802803
* else, mail readers might not be able to resolve inline references correctly.
803804
* @param plainText the plain text for the message
804805
* @param htmlText the HTML text for the message
@@ -860,16 +861,16 @@ private void setHtmlTextToMimePart(MimePart mimePart, String text) throws Messag
860861

861862
/**
862863
* Add an inline element to the MimeMessage, taking the content from a
863-
* <code>javax.activation.DataSource</code>.
864+
* {@code javax.activation.DataSource}.
864865
* <p>Note that the InputStream returned by the DataSource implementation
865866
* needs to be a <i>fresh one on each call</i>, as JavaMail will invoke
866-
* <code>getInputStream()</code> multiple times.
867-
* <p><b>NOTE:</b> Invoke <code>addInline</code> <i>after</i> {@link #setText};
867+
* {@code getInputStream()} multiple times.
868+
* <p><b>NOTE:</b> Invoke {@code addInline} <i>after</i> {@link #setText};
868869
* else, mail readers might not be able to resolve inline references correctly.
869870
* @param contentId the content ID to use. Will end up as "Content-ID" header
870871
* in the body part, surrounded by angle brackets: e.g. "myId" -> "&lt;myId&gt;".
871872
* Can be referenced in HTML source via src="cid:myId" expressions.
872-
* @param dataSource the <code>javax.activation.DataSource</code> to take
873+
* @param dataSource the {@code javax.activation.DataSource} to take
873874
* the content from, determining the InputStream and the content type
874875
* @throws MessagingException in case of errors
875876
* @see #addInline(String, java.io.File)
@@ -889,11 +890,11 @@ public void addInline(String contentId, DataSource dataSource) throws MessagingE
889890

890891
/**
891892
* Add an inline element to the MimeMessage, taking the content from a
892-
* <code>java.io.File</code>.
893+
* {@code java.io.File}.
893894
* <p>The content type will be determined by the name of the given
894895
* content file. Do not use this for temporary files with arbitrary
895896
* filenames (possibly ending in ".tmp" or the like)!
896-
* <p><b>NOTE:</b> Invoke <code>addInline</code> <i>after</i> {@link #setText};
897+
* <p><b>NOTE:</b> Invoke {@code addInline} <i>after</i> {@link #setText};
897898
* else, mail readers might not be able to resolve inline references correctly.
898899
* @param contentId the content ID to use. Will end up as "Content-ID" header
899900
* in the body part, surrounded by angle brackets: e.g. "myId" -> "&lt;myId&gt;".
@@ -913,14 +914,14 @@ public void addInline(String contentId, File file) throws MessagingException {
913914

914915
/**
915916
* Add an inline element to the MimeMessage, taking the content from a
916-
* <code>org.springframework.core.io.Resource</code>.
917+
* {@code org.springframework.core.io.Resource}.
917918
* <p>The content type will be determined by the name of the given
918919
* content file. Do not use this for temporary files with arbitrary
919920
* filenames (possibly ending in ".tmp" or the like)!
920921
* <p>Note that the InputStream returned by the Resource implementation
921922
* needs to be a <i>fresh one on each call</i>, as JavaMail will invoke
922-
* <code>getInputStream()</code> multiple times.
923-
* <p><b>NOTE:</b> Invoke <code>addInline</code> <i>after</i> {@link #setText};
923+
* {@code getInputStream()} multiple times.
924+
* <p><b>NOTE:</b> Invoke {@code addInline} <i>after</i> {@link #setText};
924925
* else, mail readers might not be able to resolve inline references correctly.
925926
* @param contentId the content ID to use. Will end up as "Content-ID" header
926927
* in the body part, surrounded by angle brackets: e.g. "myId" -> "&lt;myId&gt;".
@@ -939,14 +940,14 @@ public void addInline(String contentId, Resource resource) throws MessagingExcep
939940

940941
/**
941942
* Add an inline element to the MimeMessage, taking the content from an
942-
* <code>org.springframework.core.InputStreamResource</code>, and
943+
* {@code org.springframework.core.InputStreamResource}, and
943944
* specifying the content type explicitly.
944945
* <p>You can determine the content type for any given filename via a Java
945946
* Activation Framework's FileTypeMap, for example the one held by this helper.
946947
* <p>Note that the InputStream returned by the InputStreamSource implementation
947948
* needs to be a <i>fresh one on each call</i>, as JavaMail will invoke
948-
* <code>getInputStream()</code> multiple times.
949-
* <p><b>NOTE:</b> Invoke <code>addInline</code> <i>after</i> <code>setText</code>;
949+
* {@code getInputStream()} multiple times.
950+
* <p><b>NOTE:</b> Invoke {@code addInline} <i>after</i> {@code setText};
950951
* else, mail readers might not be able to resolve inline references correctly.
951952
* @param contentId the content ID to use. Will end up as "Content-ID" header
952953
* in the body part, surrounded by angle brackets: e.g. "myId" -> "&lt;myId&gt;".
@@ -960,7 +961,7 @@ public void addInline(String contentId, Resource resource) throws MessagingExcep
960961
* @see #addInline(String, javax.activation.DataSource)
961962
*/
962963
public void addInline(String contentId, InputStreamSource inputStreamSource, String contentType)
963-
throws MessagingException {
964+
throws MessagingException {
964965

965966
Assert.notNull(inputStreamSource, "InputStreamSource must not be null");
966967
if (inputStreamSource instanceof Resource && ((Resource) inputStreamSource).isOpen()) {
@@ -974,13 +975,13 @@ public void addInline(String contentId, InputStreamSource inputStreamSource, Str
974975

975976
/**
976977
* Add an attachment to the MimeMessage, taking the content from a
977-
* <code>javax.activation.DataSource</code>.
978+
* {@code javax.activation.DataSource}.
978979
* <p>Note that the InputStream returned by the DataSource implementation
979980
* needs to be a <i>fresh one on each call</i>, as JavaMail will invoke
980-
* <code>getInputStream()</code> multiple times.
981+
* {@code getInputStream()} multiple times.
981982
* @param attachmentFilename the name of the attachment as it will
982983
* appear in the mail (the content type will be determined by this)
983-
* @param dataSource the <code>javax.activation.DataSource</code> to take
984+
* @param dataSource the {@code javax.activation.DataSource} to take
984985
* the content from, determining the InputStream and the content type
985986
* @throws MessagingException in case of errors
986987
* @see #addAttachment(String, org.springframework.core.io.InputStreamSource)
@@ -989,16 +990,21 @@ public void addInline(String contentId, InputStreamSource inputStreamSource, Str
989990
public void addAttachment(String attachmentFilename, DataSource dataSource) throws MessagingException {
990991
Assert.notNull(attachmentFilename, "Attachment filename must not be null");
991992
Assert.notNull(dataSource, "DataSource must not be null");
992-
MimeBodyPart mimeBodyPart = new MimeBodyPart();
993-
mimeBodyPart.setDisposition(MimeBodyPart.ATTACHMENT);
994-
mimeBodyPart.setFileName(attachmentFilename);
995-
mimeBodyPart.setDataHandler(new DataHandler(dataSource));
996-
getRootMimeMultipart().addBodyPart(mimeBodyPart);
993+
try {
994+
MimeBodyPart mimeBodyPart = new MimeBodyPart();
995+
mimeBodyPart.setDisposition(MimeBodyPart.ATTACHMENT);
996+
mimeBodyPart.setFileName(MimeUtility.encodeText(attachmentFilename));
997+
mimeBodyPart.setDataHandler(new DataHandler(dataSource));
998+
getRootMimeMultipart().addBodyPart(mimeBodyPart);
999+
}
1000+
catch (UnsupportedEncodingException ex) {
1001+
throw new MessagingException("Failed to encode attachment filename", ex);
1002+
}
9971003
}
9981004

9991005
/**
10001006
* Add an attachment to the MimeMessage, taking the content from a
1001-
* <code>java.io.File</code>.
1007+
* {@code java.io.File}.
10021008
* <p>The content type will be determined by the name of the given
10031009
* content file. Do not use this for temporary files with arbitrary
10041010
* filenames (possibly ending in ".tmp" or the like)!
@@ -1018,13 +1024,13 @@ public void addAttachment(String attachmentFilename, File file) throws Messaging
10181024

10191025
/**
10201026
* Add an attachment to the MimeMessage, taking the content from an
1021-
* <code>org.springframework.core.io.InputStreamResource</code>.
1027+
* {@code org.springframework.core.io.InputStreamResource}.
10221028
* <p>The content type will be determined by the given filename for
10231029
* the attachment. Thus, any content source will be fine, including
10241030
* temporary files with arbitrary filenames.
10251031
* <p>Note that the InputStream returned by the InputStreamSource
10261032
* implementation needs to be a <i>fresh one on each call</i>, as
1027-
* JavaMail will invoke <code>getInputStream()</code> multiple times.
1033+
* JavaMail will invoke {@code getInputStream()} multiple times.
10281034
* @param attachmentFilename the name of the attachment as it will
10291035
* appear in the mail
10301036
* @param inputStreamSource the resource to take the content from
@@ -1035,18 +1041,18 @@ public void addAttachment(String attachmentFilename, File file) throws Messaging
10351041
* @see org.springframework.core.io.Resource
10361042
*/
10371043
public void addAttachment(String attachmentFilename, InputStreamSource inputStreamSource)
1038-
throws MessagingException {
1044+
throws MessagingException {
10391045

10401046
String contentType = getFileTypeMap().getContentType(attachmentFilename);
10411047
addAttachment(attachmentFilename, inputStreamSource, contentType);
10421048
}
10431049

10441050
/**
10451051
* Add an attachment to the MimeMessage, taking the content from an
1046-
* <code>org.springframework.core.io.InputStreamResource</code>.
1052+
* {@code org.springframework.core.io.InputStreamResource}.
10471053
* <p>Note that the InputStream returned by the InputStreamSource
10481054
* implementation needs to be a <i>fresh one on each call</i>, as
1049-
* JavaMail will invoke <code>getInputStream()</code> multiple times.
1055+
* JavaMail will invoke {@code getInputStream()} multiple times.
10501056
* @param attachmentFilename the name of the attachment as it will
10511057
* appear in the mail
10521058
* @param inputStreamSource the resource to take the content from
@@ -1059,7 +1065,7 @@ public void addAttachment(String attachmentFilename, InputStreamSource inputStre
10591065
*/
10601066
public void addAttachment(
10611067
String attachmentFilename, InputStreamSource inputStreamSource, String contentType)
1062-
throws MessagingException {
1068+
throws MessagingException {
10631069

10641070
Assert.notNull(inputStreamSource, "InputStreamSource must not be null");
10651071
if (inputStreamSource instanceof Resource && ((Resource) inputStreamSource).isOpen()) {
@@ -1079,7 +1085,7 @@ public void addAttachment(
10791085
* @return the Activation Framework DataSource
10801086
*/
10811087
protected DataSource createDataSource(
1082-
final InputStreamSource inputStreamSource, final String contentType, final String name) {
1088+
final InputStreamSource inputStreamSource, final String contentType, final String name) {
10831089

10841090
return new DataSource() {
10851091
public InputStream getInputStream() throws IOException {

0 commit comments

Comments
 (0)