refactor(core): 重构核心分层结构并添加发布库聚合

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scottxjw
2026-08-13 15:00:39 +08:00
parent 6e197a0cce
commit a3100c73c5
45 changed files with 501 additions and 77 deletions

View File

@@ -0,0 +1,157 @@
using System.Text.RegularExpressions;
namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.Security;
/// <summary>
/// 路径验证结果
/// </summary>
public class PathValidationResult
{
public bool IsValid { get; }
public string? NormalizedPath { get; }
public string? ErrorMessage { get; }
private PathValidationResult(bool isValid, string? normalizedPath, string? errorMessage)
{
IsValid = isValid;
NormalizedPath = normalizedPath;
ErrorMessage = errorMessage;
}
public static PathValidationResult Success(string normalizedPath)
=> new(true, normalizedPath, null);
public static PathValidationResult Fail(string errorMessage)
=> new(false, null, errorMessage);
}
/// <summary>
/// 路径验证器 - 防止路径遍历攻击
/// </summary>
public class PathValidator
{
private readonly string _applicationBasePath;
private readonly HashSet<string> _allowedDirectories;
/// <summary>
/// 默认允许的输出目录
/// </summary>
private static readonly string[] DefaultAllowedDirectories = ["exports", "output", "data/exports", "data/output"];
public PathValidator(string applicationBasePath, IEnumerable<string>? allowedDirectories = null)
{
_applicationBasePath = Path.GetFullPath(applicationBasePath);
_allowedDirectories = new HashSet<string>(
(allowedDirectories ?? DefaultAllowedDirectories).Select(d => d.Replace('/', Path.DirectorySeparatorChar)),
StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// 验证输出目录路径
/// </summary>
/// <param name="outputDir">用户输入的输出目录路径</param>
/// <returns>验证结果</returns>
public PathValidationResult ValidateOutputDirectory(string outputDir)
{
// 1. 基础验证
if (string.IsNullOrWhiteSpace(outputDir))
return PathValidationResult.Fail("输出目录不能为空");
// 2. 禁止的模式检查
var forbiddenPatterns = new[]
{
"..", // 路径遍历
"~", // 用户主目录
"\\\\", // UNC 路径
};
foreach (var pattern in forbiddenPatterns)
{
if (outputDir.Contains(pattern))
{
return PathValidationResult.Fail($"路径包含禁止的模式: {pattern}");
}
}
// 3. 检查是否为绝对路径Windows 和 Unix
if (Path.IsPathRooted(outputDir) ||
Regex.IsMatch(outputDir, @"^[A-Za-z]:") || // Windows 驱动器
outputDir.StartsWith("/")) // Unix 绝对路径
{
return PathValidationResult.Fail("仅允许相对路径");
}
// 4. 规范化并验证
string normalizedPath;
try
{
normalizedPath = Path.GetFullPath(Path.Combine(_applicationBasePath, outputDir));
}
catch (Exception ex)
{
return PathValidationResult.Fail($"路径格式无效: {ex.Message}");
}
// 5. 严格验证必须在应用目录内
if (!normalizedPath.StartsWith(_applicationBasePath, StringComparison.OrdinalIgnoreCase))
{
return PathValidationResult.Fail("路径必须在应用程序目录内");
}
// 6. 白名单检查
var relative = normalizedPath
.Substring(_applicationBasePath.Length)
.TrimStart(Path.DirectorySeparatorChar);
bool inAllowedDir = _allowedDirectories
.Any(allowed => relative.StartsWith(allowed + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) ||
string.Equals(relative, allowed, StringComparison.OrdinalIgnoreCase));
if (!inAllowedDir)
{
var allowedList = string.Join(", ", _allowedDirectories);
return PathValidationResult.Fail($"输出目录必须在允许列表中: {allowedList}");
}
return PathValidationResult.Success(normalizedPath);
}
/// <summary>
/// 简化版验证 - 仅检查路径安全性,不做白名单检查
/// </summary>
public PathValidationResult ValidatePathSafety(string path)
{
if (string.IsNullOrWhiteSpace(path))
return PathValidationResult.Fail("路径不能为空");
// 检查路径遍历
if (path.Contains(".."))
return PathValidationResult.Fail("路径包含非法的遍历字符");
// 检查绝对路径
if (Path.IsPathRooted(path) ||
Regex.IsMatch(path, @"^[A-Za-z]:") ||
path.StartsWith("/"))
{
return PathValidationResult.Fail("仅允许相对路径");
}
// 检查 UNC 路径
if (path.StartsWith("\\\\"))
return PathValidationResult.Fail("不允许 UNC 路径");
// 规范化验证
try
{
var normalized = Path.GetFullPath(Path.Combine(_applicationBasePath, path));
if (!normalized.StartsWith(_applicationBasePath, StringComparison.OrdinalIgnoreCase))
return PathValidationResult.Fail("路径超出应用程序目录范围");
}
catch (Exception ex)
{
return PathValidationResult.Fail($"路径格式无效: {ex.Message}");
}
return PathValidationResult.Success(path);
}
}