问题描述:
在使用JavaMail发送带有附件邮件的时候,附件名过长时会出现乱码的问题。
原因分析:
JavaMail在发送含有附件的邮件时,附件名称采用RFC 2231规范进行编码,RFC 2231规范定义了一种叫做Extended Filename的方法来描述附件名和附件路径,其中Extended Filename的基本语法如下:
<filename>*=utf-8''<hex-digit-encoded-string>
其中<filename>
表示未编码的文件名,hex-digit-encoded-string
表示经过URLEncode编码的字符串。但是RFC 2047规范限制了MIME头中每行最多的字符数为76个,这就导致了如果附件名过长,它会被拆分成多行,每行最多76个字符,并且从第二行开始需要加上=?utf-8?q?
这样的前缀。
解决方案:
解决JavaMail附件名字过长导致的乱码问题有以下几种方法:
- 修改邮件客户端
如果你的邮件客户端支持RFC 2231规范,并且附件名字过长时能够正确地显示,那么你可以不需要采用下面的方案,只需要修改邮件客户端的代码就可以了。但是由于邮件客户端相对不可控,所以不建议采用这个方法。
- 手动设置Content-Type头
在JavaMail中发送邮件的时候,你可以使用javax.mail.internet.MimeUtility
工具类对附件名进行编码,并将编码后的值手动设置到Content-Type头中。例如下面的代码片段:
String fileName = "这是一个特别长的文件名.txt";
String encodedFileName = MimeUtility.encodeText(fileName, "UTF-8", "B");
MimeBodyPart attachmentPart = new MimeBodyPart();
attachmentPart.setDataHandler(new DataHandler(new FileDataSource(file)));
attachmentPart.setFileName("=?UTF-8?B?" + encodedFileName + "?=");
attachmentPart.setHeader("Content-Type", "application/octet-stream;charset=UTF-8");
这里我们采用MimeUtility.encodeText(String, String, String)
方法对文件名进行编码,并将编码后的值手动设置的Content-Type头中。
- 扩展JavaMail的MimeBodyPart类
JavaMail的MimeBodyPart
类默认并没有对附件名字过长的情况进行处理,所以我们可以继承这个类,重新实现getHeader
方法来进行处理。下面是示例代码:
class UTF8MimeBodyPart extends MimeBodyPart {
@Override
protected void updateHeaders() throws MessagingException {
super.updateHeaders();
try {
Field f = getClass().getSuperclass().getDeclaredField("headers");
f.setAccessible(true);
Vector headers = (Vector) f.get(this);
for (int i = 0; i < headers.size(); i++) {
Header header = (Header) headers.get(i);
if (header.getName().equalsIgnoreCase("Content-Type")) {
String[] values = header.getValue().split(";");
for (int j = 0; j < values.length; j++) {
String value = values[j].trim();
if (value.toLowerCase().startsWith("name=")) {
String fileName = value.substring(5);
String encodedFileName = MimeUtility.encodeText(fileName, "UTF-8", "B");
values[j] = "name=\"=?UTF-8?B?" + encodedFileName + "?=\"";
}
}
header.setValue(StringUtils.join(values, ";"));
}
}
} catch (Exception e) {
throw new MessagingException("Failed to update MIME headers", e);
}
}
}
在这个示例中,我们通过重写updateHeaders()
方法来实现Content-Type头的自动编码。在该方法中,我们通过反射获取到了MimeBodyPart类中的headers对象,并通过遍历这个对象来找到Content-Type这个头。找到了之后,我们就采用MimeUtility工具类来对文件名进行编码,然后将编码后的结果更新到Content-Type头中。
示例说明:
假设现在我们要发送一个包含一个特别长文件名的附件的邮件,文件名为“互联网改变了我们的生活.docx”。
第一个示例:
我们可以使用手动设置Content-Type头的方式来发送邮件。代码如下:
public static void main(String[] args) throws Exception {
String from = "sender@domain.com";
String to = "receiver@domain.com";
String subject = "大家好";
String body = "这个邮件有一个特别长的附件名字";
Properties props = new Properties();
Session session = Session.getInstance(props, null);
// 创建一个MimeMessage对象
MimeMessage message = new MimeMessage(session);
// 设置发件人、收件人以及主题、日期等邮件相关信息
message.setFrom(new InternetAddress(from));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
message.setSubject(subject, "UTF-8");
message.setSentDate(new Date());
// 创建一个MimeMultipart对象
MimeMultipart multipart = new MimeMultipart();
// 首先创建一部分用于存放邮件正文
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(body, "UTF-8");
// 创建第二部分用于存放文件附件
File file = new File("互联网改变了我们的生活.docx");
String fileName = file.getName();
String encodedFileName = MimeUtility.encodeText(fileName, "UTF-8", "B");
MimeBodyPart attachmentPart = new MimeBodyPart();
attachmentPart.setDataHandler(new DataHandler(new FileDataSource(file)));
attachmentPart.setFileName("=?UTF-8?B?" + encodedFileName + "?=");
attachmentPart.setHeader("Content-Type", "application/octet-stream;charset=UTF-8");
multipart.addBodyPart(textPart);
multipart.addBodyPart(attachmentPart);
message.setContent(multipart);
// 将邮件发送到SMTP服务器上
Transport.send(message);
System.out.println("发送成功");
}
这里我们使用了MimeUtility.encodeText(String, String, String)
方法对文件名进行编码,并将编码后的值手动设置的Content-Type头中。
第二个示例:
我们可以使用扩展JavaMail的MimeBodyPart类的方式来发送邮件,代码如下:
public static void main(String[] args) throws Exception {
String from = "sender@domain.com";
String to = "receiver@domain.com";
String subject = "大家好";
String body = "这个邮件有一个特别长的附件名字";
Properties props = new Properties();
Session session = Session.getInstance(props, null);
// 创建一个MimeMessage对象
MimeMessage message = new MimeMessage(session);
// 设置发件人、收件人以及主题、日期等邮件相关信息
message.setFrom(new InternetAddress(from));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
message.setSubject(subject, "UTF-8");
message.setSentDate(new Date());
// 创建一个MimeMultipart对象
MimeMultipart multipart = new MimeMultipart();
// 首先创建一部分用于存放邮件正文
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(body, "UTF-8");
// 创建第二部分用于存放文件附件
File file = new File("互联网改变了我们的生活.docx");
UTF8MimeBodyPart attachmentPart = new UTF8MimeBodyPart();
attachmentPart.setDataHandler(new DataHandler(new FileDataSource(file)));
attachmentPart.setFileName("互联网改变了我们的生活.docx");
multipart.addBodyPart(textPart);
multipart.addBodyPart(attachmentPart);
message.setContent(multipart);
// 将邮件发送到SMTP服务器上
Transport.send(message);
System.out.println("发送成功");
}
这里我们创建了一个UTF8MimeBodyPart
类来继承MimeBodyPart类,并实现了自动编码的逻辑。注意,在这个示例中我们没有对文件名进行编码,而是将未编码的名称作为构造器参数传入了setFileName()
方法中,这是因为在我们重写的updateHeaders()
方法中,已经对文件名进行了自动编码。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:解决JavaMail附件名字过长导致的乱码问题 - Python技术站