杂记 › 技术笔记 › 二进制转 Base64 这一页:前缀只有 5 档,换行能解、连字符和下划线直接报错

二进制转 Base64 这一页:前缀只有 5 档,换行能解、连字符和下划线直接报错

把这一页生成的 Base64 贴到别处时,空格、换行、被吃掉的补位符都能照旧解回原字节,而把加号与斜杠写成连字符和下划线,三条解码路径一起报错;前缀实测只有五档,串整条无换行也不截断。

串递出去之后才开始出问题

事情是从一次多余的清理开始的。别人递来的 base64 中间每 76 字符断了行,我怕解码器扛不住,先手工删干净才敢往下走,对方说换行本来就不用管。我拿自己生成的串反着试,试到第二种改动才真出错:有人按 URL-safe 的习惯把 + 和 / 写成 - 和 _,atob 立刻报 InvalidCharacterError。这篇把两端对齐记一次。

整条串是 readAsDataURL 给的,前缀只有五档

模板第 80 行只有一次 readAsDataURL,页面自己不做编码:每个文件恰好新建 1 个 FileReader,产出的 body 与 btoa 编同一批字节逐字全等,连补位符个数都一样。前缀也不按后缀拼,恒为 data: 加 File 对象的 type 再加 ;base64,,22 个样本测下来前缀(含逗号)长度只有 22、23、25、29、37 这五档。

type 为空时浏览器写 application/octet-stream,不嗅探内容:同一段 70 B 的 PNG 字节按空 type 投进去,前缀退到 37 字符那一档;39587 B 的 JPEG 字节声明成 image/webp,前缀就写 image/webp,预览仍解出 1200x900。能否出图由解码器按内容决定,与统计那一栏无关。

前缀长度实测五档 22 23 25 29 37 image/png image/jpeg x-icon application/gzip octet-stream

图 1 前缀含逗号的五档,末档是空 type 的退路

body 长度恒等于 4*ceil(n/3)

编码交给浏览器之后,字符数就是纯算术。四档输入实测如下,body 长度与公式 4*ceil(n/3) 全等。

输入字节与产出字符
输入(B)body(字符)前缀含逗号
709622
97121295223
395875278423
4194304559240837

「Base64大小」只数 body,Data URI 的真实串长要再加前缀:96 字符的整串是 118,12952 的是 12975。

复制与下载拿到的是同一条无换行的串

textarea 的 maxlength 实测是 null,也没有「仅显示前 N 字符」的分支,4194304 B 的文件完整持有 5592408 字符,两个模式来回切值逐字相同。把文件拖进 二进制转 Base64 之后取走的两个出口同样不加工:复制收到的字符数与框里一致(12952、5592408 都测过,含换行全为 false),下载走 new Blob,字节数等于字符数,无 BOM 也无末尾换行。22 个样本的整串换行数 0,这一页不替你做 76 列折行。

换行、空格、缺补位符:三条路径都吞

拿这一页的 body 去解,容忍度比直觉宽。以 photo_400 的 12952 字符为基准:每 76 字符插一个换行变 13122,插 CRLF 变 13292,atob、fetch 一个 data: URI、新 Image 都照旧解回 9712 字节;任意位置插空格或制表符、首尾各加空白,结论不变。补位符也不是硬要求:去掉 = 后 12950 字符仍得 9712 字节,那张 70 B 的 PNG 从 96 变 94 也一样。

但连字符和下划线一档都不容

宽容只到空白与补位符为止。把 + 换成 -、/ 换成 _ 之后,atob 报 InvalidCharacterError、fetch 报 TypeError、Image 走 error 事件,三条路径全灭。实测 4 个样本里 3 个直接失败,剩下那个 100 B 纯文本样例本来不含 + 也不含 /,替换后串一个字没变,只是没被改坏。

更隐蔽的是查询串:photo_400 的 body 里 + 出现 119 次、/ 出现 139 次,塞进 URLSearchParams 再取出,长度仍是 12952 却与原串不全等——加号被解成了空格,随后 atob 报错。拿长度做检查在这里完全失效。

同一串 body 的五种送法 原样 12952 解回 9712 字节 每 76 字符换行 13122 解回 9712 字节 换成 CRLF 13292 解回 9712 字节 去掉末尾 = 剩 12950 解回 9712 字节 加号斜杠写成 -_ 三条路径全部报错 空白被容忍,换字母表不容

图 2 改动后的解码结果,绿色是能解回的四种送法

页面上那几个统计数各自的口径

三个数各按各的算法。formatSize(第 140-142 行)用 1024 进制,KB 与 MB 固定 2 位小数,9712 显示成 9.48 KB;「Base64大小」(第 90-91 行)取 body 的字符数、不含前缀;「增大」(第 92 行)是增量比,实测从 0.0% 摆到 50.0%:16 B 显示 50.0%、70 B 显示 37.1%、4194304 B 显示 33.3%,0 字节被钉成 0.0%。显示值之间不能互推:12.65 除以 9.48 是 1.3344,那一行的增大显示 33.4%。

这一页没有解码入口

上面那组容忍度是浏览器侧对这一页所产出串的表现,不是这一页的功能:真实存在的输入只有第 11 行那个 accept 为 null 的 input type=file,第 35 行的输出框是 readonly,没有第二个可输入文本的控件;拖拽与选择两处都只用第 1 个文件(第 170、155 行),也没有批量。要把一串 base64 转回文件得另找解码方向。至于编码侧为什么会变大、末尾的 = 怎么来,base64 编码为什么会让文件变大 那篇已经记过,不复述。

另外几种会让人以为页面坏了的输入

伪后缀和坏图都拿不到错误:72 B 的坏 PNG 照样产出 96 字符、照样打开预览容器,整串喂 Image 报 error,页面上只留浏览器自带的破图标(第 64 行只判断 type 是否以 image/ 开头,不看解码结果)。反过来,类型写成 text/plain 的 PNG 字节容器是关的,可整串喂给 Image 仍解出 1x1。

0 字节更别扭:纯 Base64 下 body 长为 0,第 117、124 行双双短路;切到 Data URI 才有 37 字符,复制能收到 37,下载是个只含前缀的 37 字节 txt。

还常碰到的几件事

Q:贴到别处时换行要先手动删掉吗?

浏览器侧不用。12952 折成 13122 或 13292 都照旧解回 9712 字节。会出事的是传输那一层:串一进查询串,加号先被解成空格。

Q:顺手改成 URL-safe 形态发出去行不行?

先确认对面按哪套字母表解。这次 4 个样本里 3 个改成 -_ 之后直接报错,浏览器不会替你把它们认回来,接收侧得显式做一次反向替换。

Q:0 字节文件为什么复制按钮没反应?

值长为 0 时第 117 行的短路先命中,剪贴板一次都没被调用;切到 Data URI 才有 37 字符可复制,而那 37 个字符全是前缀。

📎 相关文章

16 组开关实测 HTML 去标签:报 19 个标签,实际删掉 148 个字符 2026-10-30 SVG 转 PNG 后外链图丢了一半:同一份 594 B 文件预览有 3 张图、成品只剩 1 张 2026-10-30 给应用做商店介绍图,点导出拿到的却是 1242×2688:8 档画布预设的像素账、噪点 24.83 倍体积与默认被裁掉 134.93 px 的手机框 2026-10-30 JSON 转表格的表头到底从哪来:键并集、点号展平与 CSV 引号的真实规则 2026-10-30 取消勾选「保留注释」后,format-js 把 24 字符 URL 压成 13 字符残缺码 2026-10-30