背景:

我的网站有一个类似 TODO 的服务,功能就是前端传入一个字符串,后端进行 base64 编码,前端展示的时候解码。平时主要都是存一些英文链接什么的,今天偶然传入一段中文后,页面报错。

排查:

报错是 URI malformed,说明应该是使用 URI 相关函数出了问题,看了后端返回的内容,拿去在别的网站进行解码,正常,而且在这个网站编码后,与后端的结果一致。因此跑去翻看网站代码:

1
2
3
4
5
6
export function base64ToPlain(base64: string) {
const utf8 = [...window.atob(base64)]
.map((item) => "%" + item.charCodeAt(0).toString(16))
.join("");
return decodeURIComponent(utf8);
}

这是解码的函数,定位到应该是解码decodeURIComponent出现了问题,断点调试发现,utf8 是下面这样的字符串:
"%e4%b8%80%e4%b8%aa%e9%a1%b9%e7%9b%ae%ef%bc%9b%a%e8%87%b3%e5%b0%91"

十分眼熟哦,这不就是小写后的 URI 编码的样子吗,难道是因为大小写,于是果断加上toUpperCase(),但是没起作用,想想也是,之前都是小写,肯定大小写不敏感。那问题应该是出在这串字符上,格式会不会有错误呢。仔细过了一遍,还真有!中间有一个%a。经过排查,这个字符在换行回车的时候会出现,单独对回车编码,发现是%0A。所以报错原因就是少了个 0

解决:

既然问题找到,那么就看如何解决,不难看出代码中item.charCodeAt(0).toString(16),在转 16 进制时,会将 10 转为 a 而不是 0a,因此,我们想办法在前面补 0 就可以了,可以做判断后补 0,也可以统一补 0,再使用 slice 来取后两位即可,代码如下:

1
2
3
4
5
6
export function base64ToPlain(base64: string) {
const utf8 = [...window.atob(base64)]
.map((item) => "%" + ("0" + item.charCodeAt(0).toString(16)).slice(-2))
.join("");
return decodeURIComponent(utf8);
}

补充:LF 换行是%0A,CRLF 换行是%0D%0A