ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

QQ邮箱炸啦,我的附件名怎么乱码?解决javaMail发送QQ邮件,附件名乱码的问题

2021-04-02 20:29:21  阅读:287  来源: 互联网

标签:QQ String encoding ASCII charset 乱码 附件 ascii string


解决javaMail发送QQ邮件,附件名乱码的问题

问题背景

项目里面使用javaMail发QQ邮件,日志显示我们这边传过去的附件名是正常的,个别名字会出现乱码。
客户不断反馈,悟空面对这个问题在搞了两天无果后,只能求救于部门老大。老大看了看我可怜巴巴的眼神,拍着胸脯保证交给他了,结果在他debug过无数层源码,打开无数个百度页后,告诉不好解决,先延期吧。
悟空没有办法,只能拿过来自己再一点一滴debug看下,期望能解出来这个问题。

具体问题

首先给大家看下我们的问题,这个是乱码的邮件压缩包
龙珠悟空
正常的应该是这样的:
龙珠悟空

解决思路

  1. 观察正常得字母和汉字的压缩包不会有问题,有问题的是这种带有特殊符号的压缩包。有同学说直接禁止这样命名不就好了嘛,客户是上帝呀,客户要这样命名那也没有办法。所以解决问题才是正解。
  2. degbug查看发送压缩包前,传入的压缩包名字是正确的,考虑是QQ邮箱有问题,或者是名字的编码有问题,将发送方法改为发送到163邮箱,发现也有该问题。排除QQ邮箱的问题,肯定是压缩包处理时对名字的处理有问题。二话不说,悟空马上考试看源码

源码Debug

debug源码查看javaMail附件的处理方法MimeMessageHelper.addAttachment ,一路点到附件名的处理方法MimeUtility.encodeWord方法

private static String encodeWord(String string, String charset,
				     String encoding, boolean encodingWord)
			throws UnsupportedEncodingException {

	// If 'string' contains only US-ASCII characters, just
	// return it.
	int ascii = checkAscii(string);
	if (ascii == ALL_ASCII)
	    return string;

	// Else, apply the specified charset conversion.
	String jcharset;
	if (charset == null) { // use default charset
	    jcharset = getDefaultJavaCharset(); // the java charset
	    charset = getDefaultMIMECharset(); // the MIME equivalent
	} else // MIME charset -> java charset
	    jcharset = javaCharset(charset);

	// If no transfer-encoding is specified, figure one out.
	if (encoding == null) {
	    if (ascii != MOSTLY_NONASCII)
		encoding = "Q";
	    else
		encoding = "B";
	}

	boolean b64;
	if (encoding.equalsIgnoreCase("B")) 
	    b64 = true;
	else if (encoding.equalsIgnoreCase("Q"))
	    b64 = false;
	else
	    throw new UnsupportedEncodingException(
			"Unknown transfer encoding: " + encoding);

	StringBuilder outb = new StringBuilder(); // the output buffer
	doEncode(string, b64, jcharset, 
		 // As per RFC 2047, size of an encoded string should not
		 // exceed 75 bytes.
		 // 7 = size of "=?", '?', 'B'/'Q', '?', "?="
		 75 - 7 - charset.length(), // the available space
		 "=?" + charset + "?" + encoding + "?", // prefix
		 true, encodingWord, outb);

	return outb.toString();
    }

上面的代码我们可以看出,javaMail在对名字处理之前,首先对传入的名字进行了编码的判断,int ascii = checkAscii(string);encoding == null的情况下,对将要对名字进行的编码指定了encoding方法,如果encoding.equalsIgnoreCase("B")则进行Base64编码,发送给QQ邮箱。
此刻我们没有指定encoding,debug进checkAscii(string);看下,会默认什么编码

 static int checkAscii(String s) {
	int ascii = 0, non_ascii = 0;
	int l = s.length();

	for (int i = 0; i < l; i++) {
	    if (nonascii((int)s.charAt(i))) // non-ascii
		non_ascii++;
	    else
		ascii++;
	}

	if (non_ascii == 0)
	    return ALL_ASCII;
	if (ascii > non_ascii)
	    return MOSTLY_ASCII;

	return MOSTLY_NONASCII;
    }

在上述方法中nonascii((int)s.charAt(i))对US-ASCII进行校验,如果全是US-ASCII码,则返回ALL_ASCII==1,如果超过一半是 US-ASCII码,则返回MOSTLY_ASCII==2,否则就返回MOSTLY_NONASCII==3
代码块一中我们可以看出只要不是MOSTLY_NONASCII==3,就会使用Q编码即非Base64编码,MOSTLY_NONASCII==3时则使用Base64编码。
此时我们再看名字"行业媒体稿件${random}.zip ",这个名字有一半以上都是可以ASCII码,所以使用了Q即非Base64编码。

doEncode(string, b64, jcharset, 
		 // As per RFC 2047, size of an encoded string should not
		 // exceed 75 bytes.
		 // 7 = size of "=?", '?', 'B'/'Q', '?', "?="
		 75 - 7 - charset.length(), // the available space
		 "=?" + charset + "?" + encoding + "?", // prefix
		 true, encodingWord, outb);

通过doEncode方法我们可以看出名字如果超过75个字符,我们会截断成两端拼接,我们的名字长度不超过75字符,不是字符长度引起的问题,继续排查。进入doEncode 代码,我们可以看到

 ByteArrayOutputStream os = new ByteArrayOutputStream();
	    OutputStream eos; // the encoder
	    if (b64) // "B" encoding
		eos = new BEncoderStream(os);
	    else // "Q" encoding
		eos = new QEncoderStream(os, encodingWord);

QEncoderStream方法对名字数据流进行了编码处理,问题点一定再编码这块,继续看源码

 public QEncoderStream(OutputStream out, boolean encodingWord) {
	super(out, Integer.MAX_VALUE); // MAX_VALUE is 2^31, should
				       // suffice (!) to indicate that
				       // CRLFs should not be inserted
				       // when encoding rfc822 headers

	// a RFC822 "word" token has more restrictions than a
	// RFC822 "text" token.
	specials = encodingWord ? WORD_SPECIALS : TEXT_SPECIALS;
    }

可以看到定义了全局变量specials 对流的字符集进行判断,继续追踪,看该变量什么时候调用

 @Override
    public void write(int c) throws IOException {
	c = c & 0xff; // Turn off the MSB.
	if (c == ' ')
	    output('_', false);
	else if (c < 040 || c >= 0177 || specials.indexOf(c) >= 0)
	    // Encoding required. 
	    output(c, true);
	else // No encoding required
	    output(c, false);
    }

这个方法将编码后的名字流进行输出,此时我们可以看到write(int c)对每个字节进行处理,output()方法里进行流拼接,很明显问题出在拼接的流之间字符集冲突乱码啦。
至此问题点发现,是由于名字时含有非ASCII造成的,所以干脆,在封装压缩包的之后,直接指定名字的编码方式即可,统一使用Base64编码。

解决方式

从上面问题发现,只需要指定压缩包名的编码方式即可,Debug源码

    public static String encodeText(String text, String charset,
				    String encoding)
			throws UnsupportedEncodingException {
	return encodeWord(text, charset, encoding, false);
    }

发现encodeWord(text, charset, encoding, false);方法可以指定名字编码方式,所以完整的发送方法为

public void sendAttachmentsMail(MailSendBo mailSendBo)
        throws MailSendException, MessagingException, UnsupportedEncodingException {
        JavaMailSenderImpl javaMailSender = initJavaMailSender(mailSendBo.getFromEmail(), mailSendBo.getAuthCode());
        MimeMessage message = javaMailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true);
        helper.setFrom(mailSendBo.getFromEmail());
        helper.setTo(mailSendBo.getToEmail());
        helper.setSubject(mailSendBo.getSubject());
        helper.setText(mailSendBo.getContent());
        List<String> filePathList = mailSendBo.getFilePathList();
        if (CollectionUtils.isNotEmpty(filePathList)) {
            for (String url : filePathList) {
                File tempFile = new File(url);
                //指定压缩包名的编码方式和字符集
                String fileName = MimeUtility.encodeText(tempFile.getName(), "UTF-8", "B");
                helper.addAttachment(fileName, tempFile);
            }
        }
        javaMailSender.send(message);
    }

下面展示一下效果:
龙珠悟空
解决问题,老大都没解决被悟空解决啦,可是怎么才能不高调的炫耀一下呢,那么就写篇文章吧

每日一语

最可怕的是想太多,做太少,而我们恰恰处于这个年纪,停下来,先走两步,也许不一定是想的那样

点关注,不迷路,更多精彩关注微信公众号

龙珠悟空

标签:QQ,String,encoding,ASCII,charset,乱码,附件,ascii,string
来源: https://blog.csdn.net/lv_dw962464/article/details/115406103

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有