70 个字节没动,类型换了 13 次
先放一个不太体面的现场:同一份 70 字节的 PNG 文件头,我把它复制成 fi_x.png、fi_x.jpg、fi_x.gif……一共 18 个后缀,内容一个字节都没改,逐份经真实文件选择器喂进 file-info。页面给出的「MIME 类型」列有 13 种不同写法,剩下 5 个后缀落到「未知」。那 70 个字节里明明写着 \x89PNG\r\n\x1a\n,这是任何一本格式书都会告诉你的 PNG 魔数,可这一列对它的态度是——看都不看。
这件事的麻烦不在于「显示得不好看」。我们把这类页面当一个「文件信息查看器」用,默认它读过文件、默认它给出的类型是从内容里推出来的,然后在下游做判断:是图片就直接渲染,是压缩包就送去解压,是未知就退回人工。而 file-info 的「MIME 类型」列其实只是把浏览器按后缀给的那串转抄到表格里——后缀是唯一的输入,魔数全程没有参与。
把 18 个后缀摊开看
完整对照如下,左列是我改出来的后缀,右列是页面那一栏给出的字符串。整份实验里唯一的变量就是文件名最后那几个字母:
| 后缀 | 页面「MIME 类型」列 | 后缀 | 页面「MIME 类型」列 |
|---|---|---|---|
.png | image/png | .json | application/json |
.jpg | image/jpeg | .csv | text/csv |
.jpeg | image/jpeg | .bin | application/octet-stream |
.txt | text/plain | .heic | 未知 |
.zip | application/x-zip-compressed | .log | 未知 |
.pdf | application/pdf | .7z | 未知 |
.md | text/markdown | .dat | 未知 |
.gif | image/gif | .xyz9 | 未知 |
.webp | image/webp | .html | text/html |
13 种类型加 5 个「未知」,凑满 18 行。.jpg 与 .jpeg 给了同一个字符串,说明这层映射本来就带合并;而 .heic、.log、.7z、.dat、.xyz9 这 5 个后缀一律落到「未知」——前 4 个并不是什么冷门格式,iPhone 照片和日志文件都算常见,只是这台机器上的浏览器没有为它们准备映射。换个环境跑同一批文件名,落到「未知」的那几个可能会变,这也是我把 文件信息读取 当成对照台而不是结论的原因之一。
魔数与后缀打架,赢的总是后缀
光看同向实验还不够,真正能定口径的是把两种格式的指纹拧着来:内容是一种格式、名字是另一种。我做了 4 组,页面给出的类型每次都跟着名字走:
JPEG 头 FF D8 FF E0 命名 fi_photo.png → image/png 22 B ZIP 头 50 4B 03 04 命名 fi_archive.jpg → image/jpeg 39 B GIF 头 47 49 46 38 39 61 改名 .zip → application/x-zip-compressed 10 B PNG 头 89 50 4E 47 0D 0A 改名 .gif → image/gif 70 B
一个字节都没改,改的只是文件名。那一列的取值口径在模板里只有一处:第 139 行的 type: f.type || '未知'——直接把 File 对象上的字符串抄进表格;而 FileReader、readAs、ArrayBuffer、DataView 这些真正会碰内容的接口,在整份模板里的命中次数各为 0,压根不存在一条可以「嗅探」的分支。所以与其叫它「MIME 类型」,不如叫「浏览器按后缀给的那串」。
它到底读了多少东西
把模板摊开看会更清楚:第 136 行把 file 对象存进数组,第 137 到 140 行只取了 4 个字段——name、size、type、lastModified,这个对象此后再也没有被读过一次。表格只有 4 列不是设计上克制,而是输入只有这 4 个。我按这个口径去核对了几件常被默认存在的事:
- 行数、字符数、词数、哈希值、编码判定、图片宽高、创建时间、权限、完整路径——这些列在模板里的命中次数都是 0。没有这一列,也就谈不上「读取失败时显示什么」,问「MD5 那格为什么是空的」是问不存在的格子。
- 没有格式白名单:
accept=出现 0 次,任何扩展名都进列表;同样也没有体积上限,实测 6442450944 字节的稀疏文件照样正常列出,显示6.00 GB,没有任何拒绝分支或降级提示。 - 没有数量上限,也没有去重:同一个 10 字节的文件喂 100 次,得到 100 行、总大小
1000 B,重名提示也没有。想靠它做「这批文件有没有重复」是白费的。 - 把整个目录喂进去只得到 1 行:
0 B加「未知」,不递归、不合并子文件大小,所以别拿它统计文件夹总大小——那条路径上它只会给你一个 0。
大小那一列走 1024,标签却写 KB
阈值全在第 163 到 166 行,是 1024 的幂;模板里 1000 出现 0 次、KiB 字样 0 次,标签却写 KB / MB / GB。于是标称 1 GB 的 1000000000 字节显示 953.67 MB,与 1073741824 差 73741824 字节,1000000 显示 976.6 KB 还停在 KB 档;进位点也在 1024,1023 仍显示 1023 B。
小数位分 3 档:B 档 0 位、KB 档 1 位、MB 与 GB 档 2 位,toFixed 的进位让读数总比实际更大一点——2047 显示 2.0 KB,1048575 显示 1024.0 KB,差 1 个字节就是进不了 MB 档。第 166 行是最后一个 return,没有 TB 档,1099511627776 显示 1024.00 GB。
三个当场会问到的细节
Q:为什么我这边的 .zip 显示的不是 application/zip?
那一列只是转抄,不做规范化。本机 Chrome 加 Windows 的 .zip 给的是 application/x-zip-compressed,.tar.gz 只按最后一段后缀给 application/x-gzip;显式传 application/zip 也照样原样显示,换台浏览器字符串就可能不同。
Q:类型和时间都读不到时,格子里放什么?
三列各走各的:type 为空串才落到「未知」,application/octet-stream 原样显示;lastModified 为 0 不做缺省判断,直接渲染成 1970-01-01 08:00:00;0 字节显示 0 B,没有「空」这种写法。
Q:导出的文本会跟着屏幕排序走吗?
不跟。第 245 行遍历的是原始数组:32 个文件按修改时间升序后屏首是 rn_01.png,导出序号 1 仍是 mx_gifmagic.zip;16 个文件那批导出 23 行、1098 个字符、1208 字节,结尾不带换行。