fix: 修复含特殊字符路径的钻带文件读取失败 — 移除 cmd /c type,改为 AnsiFileReader 直接文件IO

问题:路径含 ( ) & % ! ^ @ 等字符时报"文件路径包含不允许的命令行特殊字符",
(D2-f)、(1) 这类正常业务文件名直接命中。

根因:读取统一入口把路径拼进命令行(cmd.exe /d /c type "..."),为"防炸"在 03d9b8b
引入 ForbiddenPathChars 黑名单——并非业务需求,是实现的自我约束。
实测:真正会坏的只有 %VAR%(必坏) 与延迟展开开启时的 !VAR!,而 ( ) & ^ @ 空格在
双引号内均正常;且 type 是字节透传,这层 cmd 对当初的编码问题毫无帮助。

改动:
- 新增 AnsiFileReader.cs:FileStream + StreamReader(默认 936 解码 + BOM 探测,
  与旧 StreamReader 解码路径逐字符等价),不经命令行
- 删除 CommandTypeFileReader.cs 及其 ForbiddenPathChars 字符校验
- 路径只保留"非空"与"文件存在"校验;IO 异常包装为
  InvalidOperationException("读取文件失败:{路径}({原因})")并保留 InnerException
- 15 处调用点同步改名;更新 CLAUDE.md 与 Docs 说明

验证:14 个 demo_drl 样例新旧实现字符串/行数逐字符一致;含特殊字符路径(含 %TEMP% 字面
路径,旧实现 rc=1)可读可解析;UTF-8/UTF-16 BOM 结果与旧实现一致;
dotnet build -p:Platform=x86 0 错误且警告集与基线逐条相同。
This commit is contained in:
2026-09-20 11:30:04 +08:00
parent 2335894be7
commit b75439cff4
11 changed files with 182 additions and 101 deletions

View File

@@ -1,85 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace DrillTools
{
internal static class CommandTypeFileReader
{
private const int Gb2312CodePage = 936;
private static readonly char[] ForbiddenPathChars =
{
'&', '|', '<', '>', '^', '%', '!', '(', ')', '@', '"', '\r', '\n'
};
public static string ReadAllText(string filePath)
{
return ReadAllText(filePath, CreateAnsiEncoding());
}
public static string ReadAllText(string filePath, Encoding encoding)
{
string normalizedPath = ValidateAndNormalizePath(filePath);
using var process = new Process
{
StartInfo = CreateStartInfo(normalizedPath, encoding)
};
if (!process.Start())
throw new InvalidOperationException("无法启动文件读取进程");
var outputTask = process.StandardOutput.ReadToEndAsync();
var errorTask = process.StandardError.ReadToEndAsync();
process.WaitForExit();
string output = outputTask.GetAwaiter().GetResult();
string error = errorTask.GetAwaiter().GetResult();
if (process.ExitCode != 0)
throw new InvalidOperationException($"读取文件失败,退出代码:{process.ExitCode},错误:{error.Trim()}");
return output;
}
private static ProcessStartInfo CreateStartInfo(string filePath, Encoding encoding)
{
return new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/d /c type \"{filePath}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
StandardOutputEncoding = encoding,
StandardErrorEncoding = encoding
};
}
private static string ValidateAndNormalizePath(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException("文件路径不能为空", nameof(filePath));
if (filePath.IndexOfAny(ForbiddenPathChars) >= 0)
throw new ArgumentException("文件路径包含不允许的命令行特殊字符", nameof(filePath));
string normalizedPath = Path.GetFullPath(filePath);
if (normalizedPath.IndexOfAny(ForbiddenPathChars) >= 0)
throw new ArgumentException("文件路径包含不允许的命令行特殊字符", nameof(filePath));
if (!File.Exists(normalizedPath))
throw new FileNotFoundException($"文件不存在:{normalizedPath}", normalizedPath);
return normalizedPath;
}
private static Encoding CreateAnsiEncoding()
{
return Encoding.GetEncoding(Gb2312CodePage);
}
}
}