使用 JavaMail 阅读电子邮件内容时出现编码问题

Posted

技术标签:

【中文标题】使用 JavaMail 阅读电子邮件内容时出现编码问题【英文标题】:Encoding issue while reading the content of an email with JavaMail 【发布时间】:2012-11-03 07:58:14 【问题描述】:

我正在使用 JavaMail 1.4.1 从电子邮件帐户读取消息(我已升级到 1.4.5 版本,但遇到了同样的问题),但我遇到了内容编码问题:

POP3Message pop3message;
... 
Object contentObject = pop3message.getContent();
...   
String contentType = pop3message.getContentType();
String content = contentObject.toString();

有些消息可以正确读取,但有些消息由于编码不合适而出现奇怪的字符。我意识到它不适用于特定的内容类型。

如果 contentType 是以下任何一个,它就可以正常工作:

文本/纯文本; charset=ISO-8859-1

文本/纯文本; charset="iso-8859-1"

文本/纯文本; charset="ISO-8859-1"; 格式=“流动”

文本/纯文本; charset=windows-1252

但如果是,则不是:

文本/纯文本; charset="utf-8"

对于这个 contentType(UTF-8 之一),如果我尝试获取编码 (pop3message.getEncoding()) 我会得到 ​​p>

引用可打印

例如,对于后一种编码,我在调试器中得到字符串值(与持久化对象后在数据库中看到的方式相同):

Ubicación(而不是 Ubicación)

但是,如果我在浏览器中使用电子邮件客户端打开电子邮件,它可以毫无问题地阅读,而且是普通邮件(没有附件,只有文本),所以邮件似乎没问题。

知道如何解决这个问题吗?

谢谢。


更新 这是我添加的一段代码,用于尝试 jlordo 给出的函数 getUTF8Content()

POP3Message pop3message = (POP3Message) message;
String uid = pop3folder.getUID(message);

//START JUST FOR TESTING PURPOSES
if(uid.trim().equals("1401"))
    Object utfContent = pop3message.getContent();
    System.out.println(utfContent.getClass().getName()); // it is of type String
    //System.out.println(utfContent); // if not commmented it prints the content of one of the emails I'm having problems with.
    System.out.println(pop3message.getEncoding()); //prints: quoted-printable
    System.out.println(pop3message.getContentType()); //prints: text/plain; charset="utf-8"
    String utfContentString = getUTF8Content(utfContent); // throws java.lang.ClassCastException: java.lang.String cannot be cast to javax.mail.util.SharedByteArrayInputStream
    System.out.println(utfContentString);


//END TEST CODE

【问题讨论】:

你在哪里看到Ubicación (instead of Ubicación)?安慰?变量检查器?我怀疑一切都很好,但调试器无法显示 utf-8 字符。 @jlordo 在 Eclipse 的调试器中,我通过观察 content 变量中的内容来了解​​这一点。同样在数据库 postgresql 中,如果我进行选择,我会得到该结果。 你是从数据库中读取,还是先写入数据库再读取?数据库设置正确吗? @jlordo 如果我在数据被持久化之前就检测到问题,怎么可能是数据库的问题? @jlordo 在持久化数据之前我在调试器中观察它,我将它保存到日志中,我什至在控制台中打印它并且所有这些都以相同的方式(使用 ISO-8859- 1和windows-1252正确显示)。将其保存在数据库中后,我可以通过使用 PostgreSQL 的管理员看到完全相同的内容。您真的认为 Eclipse、控制台、日志以及后来的 PostgreSQL 管理员无法正确打印吗?我想这一定是Javamail的问题。 【参考方案1】:

您如何检测到这些消息包含“奇怪的字符”?您是否在某处显示数据?您用于显示数据的任何方法都可能无法正确处理 Unicode 字符。

第一步是确定问题是您输入了错误的字符,还是正确的字符显示不正确。您可以检查数据中每个字符的 Unicode 值(例如,在 getContent 方法返回的字符串中),以确保每个字符都具有正确的 Unicode 值。如果是这样,则问题出在您用于显示字符的方法上。

【讨论】:

我在 Eclipse 的调试器中观看,但在 postgresql 数据库中也可以看到。我不认为这是 eclipse 和 PgAdminIII 的问题。实际上,当我从该表中读取时,我再次遇到了该字段中的编码问题。 再一次,按照我的建议确定问题出在哪里。【参考方案2】:

试试这个,让我知道它是否有效:

if ( *check if utf 8 here* ) 
    content = getUTF8Content(contentObject);


// TODO take care of UnsupportedEncodingException, 
// IOException and ClassCastException
public static String getUTF8Content(Object contentObject) 
    // possible ClassCastException
    SharedByteArrayInputStream sbais = (SharedByteArrayInputStream) contentObject;
    // possible UnsupportedEncodingException
    InputStreamReader isr = new InputStreamReader(sbais, Charset.forName("UTF-8"));
    int charsRead = 0;
    StringBuilder content = new StringBuilder();
    int bufferSize = 1024;
    char[] buffer = new char[bufferSize];
    // possible IOException
    while ((charsRead = isr.read(buffer)) != -1) 
        content.append(Arrays.copyOf(buffer, charsRead));
    
    return content.toString();

顺便说一句,JavaMail 1.4.1 是必需的吗?最新版本是 1.4.5。

【讨论】:

以上是 JavaMail 在使用消息中的字符集为任何部分返回字符串时在内部执行的有效操作。 你是说这对你不起作用吗?我在没有查看来源的情况下提出了它。这从底层字节数组中读取字节。如果他们在那个数组中是错误的,那么他们是错误的,你需要检查他们是如何进入那里的。 我是说 JavaMail 已经做了同样的事情,所以没有必要在应用程序中这样做。是的,正如您所说,如果消息中包含错误的字节,则会出现其他问题。例如,创建消息的程序可能会将 iso-8859-1 字节放入消息中,但将标头中的字符集设置为“utf-8”。垃圾邮件程序经常被这样破坏。 在您的帖子中您写道“所以消息似乎还可以。”。如果消息包含错误的字节,如何正确显示? 我已经为我遇到问题的一封电子邮件尝试了这段代码,对象 contentObject 的类型是 java.lang.String。当我尝试用它调用 getUTF8Content() 时,它会在第一行抛出 ClassCastException: java.lang.ClassCastException: java.lang.String cannot be cast to javax.mail.util.SharedByteArrayInputStream【参考方案3】:

对我有用的是我调用了getContentType(),我会检查字符串中是否包含“utf”(定义用作 UTF 之一的字符集)。

如果是,我会在这种情况下区别对待内容。

private String encodeCorrectly(InputStream is) 
    java.util.Scanner s = new java.util.Scanner(is, StandardCharsets.UTF_8.toString()).useDelimiter("\\A");
    return s.hasNext() ? s.next() : "";

(从this answer on SO 对 IS 到字符串转换器的修改)

这里的重要部分是使用正确的字符集。这为我解决了这个问题。

【讨论】:

【参考方案4】:

首先你必须以这种方式根据 UTF-8 编码添加标头:

...
MimeMessage msg = new MimeMessage(session);
msg.setHeader("Content-Type", "text/html; charset=UTF-8");
msg.setHeader("Content-Transfer-Encoding", "8bit");

msg.setFrom(new InternetAddress(doConversion(from)));
msg.setRecipients(javax.mail.Message.RecipientType.TO, address);
msg.setSubject(asunto, "UTF-8");

MimeBodyPart mbp1 = new MimeBodyPart();
mbp1.setContent(text, "text/html; charset=UTF-8");
Multipart mp = new MimeMultipart();
mp.addBodyPart(mbp1);
...

但是对于'from'标题,我使用以下方法来转换字符:

public String doConversion(String original) 
    if(original == null) return null;
    String converted = original.replaceAll("á", "\u00c3\u00a1");
    converted = converted.replaceAll("Á", "\u00c3\u0081");
    converted = converted.replaceAll("é", "\u00c3\u00a9");
    converted = converted.replaceAll("É", "\u00c3\u0089");
    converted = converted.replaceAll("í", "\u00c3\u00ad");
    converted = converted.replaceAll("Í", "\u00c3\u008d");
    converted = converted.replaceAll("ó", "\u00c3\u00b3");
    converted = converted.replaceAll("Ó", "\u00c3\u0093");
    converted = converted.replaceAll("ú", "\u00c3\u00ba");
    converted = converted.replaceAll("Ú", "\u00c3\u009a");
    converted = converted.replaceAll("ñ", "\u00c3\u00b1");
    converted = converted.replaceAll("Ñ", "\u00c3\u0091");
    converted = converted.replaceAll("€", "\u00c2\u0080");
    converted = converted.replaceAll("¿", "\u00c2\u00bf");
    converted = converted.replaceAll("ª", "\u00c2\u00aa");
    converted = converted.replaceAll("º", "\u00c2\u00b0");
    return converted;

如果你需要包含一些其他字符,你可以在http://www.fileformat.info/info/charset/UTF-8/list.htm看到相应的UTF-8十六进制编码。

【讨论】:

以上是关于使用 JavaMail 阅读电子邮件内容时出现编码问题的主要内容,如果未能解决你的问题,请参考以下文章

Javamail 使用 7BIT 内容传输编码解析电子邮件正文

错误:阅读电子邮件内容时出现 javax.mail.internet.MimeMultipart

Java Mail:在没有 SSL 的端口 25 上发送电子邮件时出现 SSLHandshakeException

JavaMail 内容传输编码问题

JavaMail - 如何阅读实际的邮件内容,而不是“javax.mail.internet.MimeMultipart”

javamail 访问共享邮箱