Files
AohDrllTools/Docs/含特殊字符路径读取修复说明.md
Mr.Xia 209268ee18 fix: 还原 cmd /c type 读取并移除路径字符黑名单 — 加密钻带文件只能用 cmd type 读取
b75439c 以"命令行是多余的"为由把读取改成 FileStream+StreamReader,是错误前提:
项目存在无法用标准文件 I/O 读取的加密 txt/钻带文件(CLAUDE.md "Encrypted file reading"、
Docs/readme.md "读取加密钻带(使用cmd命令type读取)"、
Docs/刀具列表拖动排序功能说明.md "加密文件:使用 cmd 命令读取"均有记录),
该改动会导致这些文件读取失败。本次回退。

改动:
- 还原 CommandTypeFileReader.cs:cmd.exe /d /c type(ANSI/936 解码),
  失败时退出码与 stderr 保留在异常消息里
- 删除 ForbiddenPathChars 与两处字符校验(本次报错根因):
  ( ) & ^ @ 空格在双引号内对 type 安全,黑名单只会误伤 (D2-f)、(1) 等正常文件名
- 保留"非空路径""文件存在"校验与 ReadAllText(path, encoding) 重载
- 删除 AnsiFileReader.cs,15 处调用点改回
- CLAUDE.md / .trellis/spec/:恢复"加密文件必须用 cmd type"的说明,
  并标注为不可优化掉的硬约束(含 b75439c 反例)

验证:demo_drl 全部 36 个文件与 2335894 实现逐字符一致、行数一致;
含 ( ) & 空格路径实测可读(旧黑名单实现抛"文件路径包含不允许的命令行特殊字符");
cmd 失败消息含退出码与 stderr;dotnet build -p:Platform=x86 0 错误,
警告集与 HEAD 基线完全一致。
2026-09-20 13:28:37 +08:00

5.7 KiB
Raw Permalink Blame History

含特殊字符路径的钻带文件读取修复说明

修复日期:2026-09-20(任务 .trellis/tasks/09-20-restore-cmd-type-reader) 影响面:所有钻带/参数文件读取入口(共 15 处调用) 本文件替换了同日先前一版的错误结论(“不存在加密层 / 可不经命令行”),见下文“上一轮的错误结论”。

问题描述

选择路径或文件名中含特定字符的钻带/参数文件时,加载直接失败:

加载钻带文件失败:文件路径包含不允许的命令行特殊字符(Parameter 'filePath')

命中字符包括 ( ) & % ! ^ @ | < > 等。实际业务文件名很容易命中,例如:

  • 1009B-2013钻机参数表(D2-f)(A1A2厂) (1).doc
  • demo_drl (1)/xx.drl

根因

读取统一入口 CommandTypeFileReader.ReadAllText 通过 cmd.exe /d /c type "<path>" 读取文件:

FileName = "cmd.exe",
Arguments = $"/d /c type \"{filePath}\"",

因为路径会成为命令行的一部分,03d9b8b(2026-05-16)在 ValidateAndNormalizePath 中引入了一串 字符黑名单(ForbiddenPathChars = & | < > ^ % ! ( ) @ " \r \n),命中即抛异常。

这层字符黑名单是过度防御:真正会被 cmd 语义破坏的只有 %VAR% 形状片段(变量展开在双引号内同样发生) 和延迟展开开启时的 !,而 ( ) & ^ @ 空格在双引号内完全安全。黑名单把安全字符一并禁掉, 直接误伤 (D2-f)、(1) 这类正常业务文件名。

为什么必须保留 cmd.exe /d /c type(而不能改成标准文件 I/O)

项目存在部分加密的 txt/钻带文件,无法用标准文件 I/O(File.ReadAllText / FileStream / StreamReader) 直接读取,只能靠 type 命令读出明文。这条项目事实原本就记录在 CLAUDE.md:

Encrypted file reading: Uses cmd.exe /c type to read drill tape files (some files are "encrypted" and cannot be read directly via standard file I/O)

上一轮的错误结论(已被本次回退)

b75439c(2026-09-20)以“type 只是字节透传、命令行是多余的”为前提,把读取改成 FileStream + StreamReader 直读并删除 CommandTypeFileReader。该前提是错的:

  • 该提交的动机来自历史问题(.NET 6 下 Encoding.Default = UTF-8 导致 GB2312 乱码, 当年 Encoding.GetEncoding(936) 因未注册 CodePages provider 会抛异常)。编码问题确实早已由 473b2ca(注册 CodePagesEncodingProvider)解决,但**“编码问题已解决”不等于“cmd type 可以删”**—— 保留 cmd type 的真实理由是加密文件。
  • 上轮检索时关键词只用了 cmd type,而 CLAUDE.md 的记录写作 cmd.exe /c type,因此漏检, 随后复核又把这条项目事实当作“没有证据的说法”删除。

因此本次恢复 CommandTypeFileReader,并在 CLAUDE.md 与 .trellis/spec/ 中把 “必须经 cmd type 读取加密文件、禁止改成标准文件 I/O”写成硬约束,避免再次被“优化掉”。

本次改动

  1. 恢复 CommandTypeFileReader.cs(git show 2335894:CommandTypeFileReader.cs 为基线), 读取仍为 cmd.exe /d /c type "<path>",StandardOutputEncoding/StandardErrorEncoding 仍为 ANSI/936; 失败时保留退出码与 stderr:InvalidOperationException("读取文件失败,退出代码:{code},错误:{stderr}")。
  2. 删除 ForbiddenPathChars 与 ValidateAndNormalizePath 中两处字符校验(原始路径一次、规范化路径一次)。 路径只保留“非空”ArgumentException("文件路径不能为空") 与“文件存在”FileNotFoundException("文件不存在:...")。
  3. 删除 b75439c 引入的 AnsiFileReader.cs;15 处调用点由 AnsiFileReader.ReadAllText 改回 CommandTypeFileReader.ReadAllText。
  4. 保留 ReadAllText(filePath, encoding) 重载;默认编码仍为 ANSI/GB2312(code page 936)。
  5. 修正 CLAUDE.md、.trellis/spec/backend/、.trellis/spec/guides/index.md 中上一轮被改错的描述。

实测结论(Windows 10.0.26100,cmd /d /c type "<path>")

路径中的字符 实测结果 说明
( ) & ^ @ 空格 可正常读取 双引号内 cmd 不把 &/^ 当分隔符;本次黑名单移除的直接依据
%CD% / %TEMP% / %USERNAME% 读取失败(rc=1) 变量展开在双引号内依然发生 —— 已知残留限制
!CD! 默认可读;/v:on 时失败 依赖延迟展开开关 —— 已知残留限制
< > ` "` 与 CR/LF 在 Windows 文件名中本就非法
含 0x1A 等二进制字节 字节完全一致 type 是字节透传,不做任何转换

已知残留限制(cmd 语义,本次不解决)

  • 路径中出现 %VAR% 形状片段时会变量展开,导致读取失败。
  • 路径中的 ! 仅在延迟展开开启(cmd /v:on 或注册表 DelayedExpansion=1)时失败。

这两类片段无法通过字符黑名单与正常文件名区分,故不拦截;如需彻底解决需另立任务 (例如改用短路径/临时副本等 cmd 之外的方案),本次范围不含。

后续约定

  • 钻带/参数文件读取统一走 CommandTypeFileReader.ReadAllText:必须保留 cmd.exe /d /c type, 不要为了“去掉命令行”改成 File.ReadAllText / File.ReadAllBytes / FileStream / StreamReader (加密文件会读取失败,b75439c 已试过并被回退)。
  • 不要再对文件路径做字符黑名单校验:双引号内 ( ) & ^ @ 与空格对 type 安全,黑名单只会误伤正常文件名。
  • 需要更换编码时使用 ReadAllText(filePath, encoding) 重载,不要另起第二套读取逻辑。