javax.mail 处理邮件时由于content-type内容不合标准引起的错误

工作中遇到了使用javax.mail 接收邮件附件时在处理头信息中content-type 时报错的问题,将问题和解决方法记录下来


一开始使用的是javax.mail 1.4 版本,出现的错误代码及说明:

这是一段获的邮件附件的代码,在执行for 语句的 multipart.getCount() 方法时报错

private void unwrapMltipart(Multipart multipart, List<Part> list) throws Exception {  
        // 依次处理各个部分  
        for (int j = 0, n = multipart.getCount(); j < n; j++) {  
            Part part = multipart.getBodyPart(j);
            if (part.getContent() instanceof Multipart) {  
                Multipart p = (Multipart) part.getContent();// 转成小包裹  
                unwrapMltipart(p, list);  
            } else {  
                String disposition = part.getDisposition();
                if(disposition != null && disposition.equals(Part.ATTACHMENT)){
                    list.add(part);
                }
            }  
        }  
    }

javax.mail.internet.ParseException: Expected '=', got "null"
	at javax.mail.internet.ParameterList.<init>(ParameterList.java:247)
	at javax.mail.internet.ContentType.<init>(ContentType.java:113)
	at javax.mail.internet.MimeMultipart.parsebm(MimeMultipart.java:737)
	at javax.mail.internet.MimeMultipart.parse(MimeMultipart.java:466)
	at javax.mail.internet.MimeMultipart.getCount(MimeMultipart.java:242)


经过代码跟踪发现 

multipart.getCount()  方法在获取part数量时首先会分析 邮件header里的Content-Type信息,里面包含了邮件格式、boundary等信息,在boundary之后每多一个信息需要用;key=value 的格式添加。

而程序报错时处理的邮件header 的Content-Type 的值却是这样的:

Content-Type: multipart/mixed;
 boundary="=_NextPart_2rfkindysadvnqw3nerasdf";gb2312

在读取完boundary 属性的值后,把gb2312当成了一个属性名称,按照规定后面应该出现 =xxx ,因此报错。

经过在网上搜索,在 stackoverflow、community.oracle.com  找到类似或相同问题,有位貌似是javax.mail 这段相关代码的作者 Bill Shannon的回答非常有帮助。原因是收到的邮件Content-Type 不符合MIME的(RFC 2045, RFC 2046, and RFC 2047)标准造成javax.mail处理时抛异常,可以在使用content-type前修改它以符合标准,他建议应该告诉这封邮件的发送服务者改到这个错误。

解决办法是使用 javax.mail doc中 Package javax.mail.internet Description 里介绍的 mail.mime.contenttypehandler 环境变量。
javax.mail 1.4版本还没有这个变量的功能,我看了1.4.4和1.4.7 版本都有这个功能了。


 这是doc对使用这个环境变量的说明,可以操作系统中添加,或者用System.setProperty("mail.mime.contenttypehandler", "XXX"); 

mail.mime.contenttypehandler                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               String                                                                                                                                                                                                                           In some cases JavaMail is unable to process messages with an invalid Content-Type header. The header may have incorrect syntax or other problems. This property specifies the name of a class that will be used to clean up the Content-Type header value before JavaMail uses it. The class must have a method with this signature: public static String cleanContentType(MimePart mp, String contentType) Whenever JavaMail accesses the Content-Type header of a message, it will pass the value to this method and use the returned value instead. The value may be null if the Content-Type header isn't present. Returning null will cause the default Content-Type to be used. The MimePart may be used to access other headers of the message part to determine how to correct the Content-Type. Note that the Content-Type handler doesn't affect the getHeader method, which still returns the raw header value. Note also that the handler doesn't affect the IMAP provider; the IMAP server is responsible for returning pre-parsed, syntactically correct Content-Type information.

 根据doc介绍的大意我在代码中添加了 public static String cleanContentType(MimePart mp, String contentType) 这个方法,这个方法可以处理contentType返回值是经过过滤后的contentType,在实践中又出现了不能云行这段方法的问题,需要注意在设置这个mail.mime.contenttypehandler 环境变量时 值是包含cleanContentType方法的类名包含包名也要写上,javax.mail中的com.sun.mail.util.MimeUtil类是具体负责调用这段方法的地方,可以看到其中用反射调用这个方法。
在cleanContentType方法对contentType做一下验证返回一个正确的值,每当javax.mail有地方用到contentType都会经过这个方法。这样问题就解决。 


相关信息:

1. MIME标准 http://www.ietf.org/rfc/rfc2045.txt 上可以看到Syntax of the Content-Type Header Field 的说明:

In the Augmented BNF notation of RFC 822, a Content-Type header field
   value is defined as follows:

     content := "Content-Type" ":" type "/" subtype
                *(";" parameter)
                ; Matching of media type and subtype
                ; is ALWAYS case-insensitive.

     type := discrete-type / composite-type

     discrete-type := "text" / "image" / "audio" / "video" /
                      "application" / extension-token

     composite-type := "message" / "multipart" / extension-token

     extension-token := ietf-token / x-token

     ietf-token := <An extension token defined by a
                    standards-track RFC and registered
                    with IANA.>

     x-token := <The two characters "X-" or "x-" followed, with
                 no intervening white space, by any token>

     subtype := extension-token / iana-token

     iana-token := <A publicly-defined extension token. Tokens
                    of this form must be registered with IANA
                    as specified in RFC 2048.>

     parameter := attribute "=" value

     attribute := token
                  ; Matching of attributes
                  ; is ALWAYS case-insensitive.

     value := token / quoted-string

     token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
                 or tspecials>

     tspecials :=  "(" / ")" / "<" / ">" / "@" /
                   "," / ";" / ":" / "\" / <">
                   "/" / "[" / "]" / "?" / "="
                   ; Must be in quoted-string,
                   ; to use within parameter values

2. 网上对这个问题的回答:

https://community.oracle.com/message/9996717#9996717 、 http://stackoverflow.com/questions/18737987/javamail-correct-incoming/18748957






  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
获取邮件内容并删除引用部分的步骤如下: 1. 首先获取邮件内容,可以使用`MimeMessage.getContent()`方法。 2. 如果该邮件是一个多部分邮件,则需要递归地获取所有邮件部分的内容。可以使用`Multipart.getCount()`获取部分数量,然后使用`Multipart.getBodyPart(int index)`获取每个部分的内容。 3. 对于每个邮件部分,可以使用`Part.isMimeType(String mimeType)`方法判断其类型。如果是`text/plain`或`text/html`类型,则是邮件内容部分,可以使用`Part.getContent()`获取内容。 4. 如果是引用部分,则可以根据邮件中引用的特定字符串进行识别,比如`">>>"`或者`"-----Original Message-----"`。一旦识别,即可删除该部分内容。 5. 最后将处理后的内容拼接起来即可。 下面是一个示例代码片段,演示如何获取邮件内容并删除引用部分: ```java public String getCleanContent(Part part) throws MessagingException, IOException { if (part.isMimeType("text/plain") || part.isMimeType("text/html")) { // 如果是文本类型 String content = part.getContent().toString(); // TODO: 处理文本内容 return content; } else if (part.isMimeType("multipart/*")) { // 如果是多部分类型 Multipart multipart = (Multipart) part.getContent(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < multipart.getCount(); i++) { BodyPart bodyPart = multipart.getBodyPart(i); String cleanContent = getCleanContent(bodyPart); if (cleanContent != null && !cleanContent.isEmpty()) { sb.append(cleanContent).append("\n"); } } return sb.toString(); } else if (part.isMimeType("message/rfc822")) { // 如果是嵌套的邮件 return getCleanContent((Part) part.getContent()); } else { // 其他类型暂不处理 return null; } } public String getEmailContent(Message message) throws MessagingException, IOException { // 获取邮件内容 Object content = message.getContent(); if (content instanceof Multipart) { // 如果是多部分邮件 Multipart multipart = (Multipart) content; StringBuilder sb = new StringBuilder(); for (int i = 0; i < multipart.getCount(); i++) { BodyPart bodyPart = multipart.getBodyPart(i); String cleanContent = getCleanContent(bodyPart); if (cleanContent != null && !cleanContent.isEmpty()) { sb.append(cleanContent).append("\n"); } } return sb.toString(); } else if (content instanceof String) { // 如果是纯文本邮件 return (String) content; } else { // 其他类型暂不处理 return null; } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值