浏览器导入日志的边界验收:UTF-8、字节上限与失败关闭
作者:lgbisha。本文使用 AI 辅助整理,基于本人已公开项目「隐迹 · 离线日志脱敏」1.0.0 的实际源码和 2026 年 10 月 24 日浏览器实测。所有输入均为专门生成的虚构文本或字节,不含真实日志和有效凭据。
上一篇介绍替换区间与结果失效,这次只讨论更早的一道入口:浏览器选择文件之后,什么内容可以进入文本处理流程?文件后缀正确不等于编码正确,字符数量也不等于文件字节数。入口如果悄悄修复或截断输入,后续处理即使正常运行,也可能处理了另一份内容。
一、把导入契约写成可检查的顺序
项目中的文件导入位于 assets/app.js,规则层的输入检查位于 assets/redactor.js。当前顺序是:
- 用户主动选择文件,使旧结果和导出状态失效。
- 检查
file.size,超过 1 MiB 即拒绝,不先读取整个文件再判断。 - 调用
file.arrayBuffer()读取字节,并核对读取期间页面版本是否改变。 - 用
new TextDecoder("utf-8", { fatal: true })解码;非法 UTF-8 明确报错,不把替代字符当成原文。 - 把解码结果交给
validateInput,再次检查文本边界,包括空字节。 - 仅在全部通过后填入原始日志框。导入成功仍不是脱敏成功,需要用户另行执行处理。
其中关键部分可以简化为:
if (file.size > MAX_INPUT_BYTES) {
throw new Error("文件超过字节上限");
}
const buffer = await file.arrayBuffer();
const decoded = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
validateInput(decoded);
这段示意省略了界面更新和异步版本校验,不能直接替换项目完整函数。入口也不靠 MIME 类型或 .txt 后缀证明文件一定是纯文本。
二、1 MiB 限的是字节,不是 JavaScript 字符串长度
本项目的上限为 1,048,576 字节。恰好等于上限允许导入,大于上限才拒绝。这使边界测试至少需要覆盖上限前后,而不是只试一个很小的英文文件。
本次还准备了 349,526 个“中”字:其 UTF-8 编码共 1,048,578 字节,虽然字符数量明显少于一百万,仍然超过上限。浏览器实际显示“文件超过 1 MiB”。
可用以下 Node.js 代码复核测试数据的字节数,不需要任何真实文件:
const text = "中".repeat(349526);
console.log(text.length);
console.log(Buffer.byteLength(text, "utf8"));
本次输出分别为 349526 和 1048578。文件导入前检查的是文件字节数,输入框计量也应使用 UTF-8 字节长度,不能直接把 text.length 当作字节数。
三、非法编码和空字节是两类拒绝
第一种测试输入只有两个字节:0xC3 0x28。它不是合法 UTF-8。当前严格解码会失败,界面提示“文件不是有效的 UTF-8 文本,请转换编码后重新导入”。这比默默生成替代字符更便于用户发现文件编码问题。
第二种输入是 demo、一个 0x00 字节和 end,共 8 字节。它能被 UTF-8 解码,但违反本工具的纯文本输入契约,后续检查提示“内容包含空字节,请使用 UTF-8 纯文本文件”。
因此,“解码成功”不等于“输入校验成功”。这里拒绝空字节是本工具的产品选择,不是说 UTF-8 本身禁止该码位,也不是通用恶意文件检测。
四、六组真实浏览器结果
2026 年 10 月 24 日,我用本机回环地址提供未修改的静态项目,在当前 Chromium 会话中通过文件选择控件导入以下合成文件。每组测试前清空页面,完成后核对提示、原文框与两个导出按钮。
| 输入 | 文件字节数 | 导入结果 |
|---|---|---|
| 含中文与虚构 password 字段的合法 UTF-8 | 39 | 接受,等待用户执行脱敏 |
字节 C3 28 |
2 | 拒绝:不是有效 UTF-8 |
demo + 空字节 + end |
8 | 拒绝:包含空字节 |
| 超过上限的 ASCII | 1,048,577 | 拒绝:超过 1 MiB |
| 恰好达到上限的 ASCII | 1,048,576 | 接受,等待用户执行脱敏 |
| 349,526 个“中”字 | 1,048,578 | 拒绝:超过 1 MiB |
六组结果均符合本次预期。四组被拒绝的输入没有写入原文框;两组被接受的输入也没有直接变成可导出结果。所有组结束时,“复制结果”和“下载 .txt”均为禁用,因为测试没有执行脱敏。
这里的“六组”仅指文件导入场景,不是新增六条引擎单元测试,也没有重新宣称整套单元测试数量。恰好上限的样例只验证导入接受,不证明最大输入下的脱敏性能。
五、失败关闭需要连同界面状态一起验证
错误提示正确还不够。如果界面报“编码不合法”,却仍保留上一份可导出的结果,用户可能继续复制旧内容。因此本次验收同时记录了原文是否填入、复制是否禁用以及下载是否禁用。
项目在导入开始时就使旧结果失效,并在异步读取后比对页面版本。如果读取期间用户改动输入,过期回调不应覆盖新的状态。这一机制与字节、编码检查解决不同的问题:前者防止时序覆盖,后者约束当前输入。本文六组测试没有人为延迟文件读取,因此没有把异步竞争场景算作已实测通过。
六、“本地处理”的证据要说明观察窗口
在本次六组交互开始后,我监听了该页面发起的网络请求,交互结束后停止监听;观察窗口内记录为零条请求。这能支持“本次文件导入没有观察到页面新发出的网络请求”,不能外推为对浏览器扩展、操作系统、剪贴板或所有未来版本的安全审计。
静态资源在开始观察前已加载。离线工具仍须检查实际运行的源码和来源,不能仅凭页面写着“不上传”就放入生产凭据。输入边界校验也不等于脱敏覆盖率保证;最终输出仍需按分享对象人工复核。
结语
文本工具的入口应明确区分文件字节、解码结果和业务允许的文本。对超限、编码错误和空字节分别给出可操作提示,且在失败时关闭导出通道,比“能打开一个正常文件”的验收更完整。
项目公开于 GitCode:LGBISHA/offline-log-redactor。本次未修改产品代码,没有使用真实凭据,也没有把一次浏览器测试写成全面安全保证。
- 点赞
- 收藏
- 关注作者
评论(0)