mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
- 在 GodotYamlConfigLoader 中增加对路径中冒号字符的验证,防止 Windows 无效名称和 ADS 类似语法 - 新增 GodotYamlConfigTableSource 类用于描述配置表来源信息,并实现安全路径验证 - 添加对配置路径和 schema 路径的严格安全检查,拒绝包含根路径、遍历标记或冒号字符的路径 - 扩展测试用例覆盖多种不安全路径场景,包括路径遍历、绝对路径前缀和冒号字符 - 为新功能添加完整的单元测试验证安全路径验证逻辑
121 lines
4.3 KiB
C#
121 lines
4.3 KiB
C#
using System.IO;
|
|
|
|
namespace GFramework.Godot.Config;
|
|
|
|
/// <summary>
|
|
/// 描述一个 Godot YAML 配置表在资源目录中的来源信息。
|
|
/// </summary>
|
|
public sealed class GodotYamlConfigTableSource
|
|
{
|
|
/// <summary>
|
|
/// 初始化一个配置表来源描述。
|
|
/// </summary>
|
|
/// <param name="tableName">运行时表名称。</param>
|
|
/// <param name="configRelativePath">
|
|
/// 相对配置根目录的 YAML 目录。
|
|
/// 该路径必须保持为无根相对路径,且不能包含 <c>.</c>、<c>..</c>、<c>res://</c>、<c>user://</c>、<c>:</c>
|
|
/// 或磁盘根路径前缀。
|
|
/// </param>
|
|
/// <param name="schemaRelativePath">
|
|
/// 相对配置根目录的 schema 文件路径;未启用 schema 时为空。
|
|
/// 如果提供,同样必须保持为无根相对路径,且不能包含 <c>.</c>、<c>..</c>、<c>:</c> 或任何绝对路径前缀。
|
|
/// </param>
|
|
/// <exception cref="ArgumentException">
|
|
/// <paramref name="tableName" />、<paramref name="configRelativePath" /> 或 <paramref name="schemaRelativePath" />
|
|
/// 不满足非空白且安全相对路径的约束时抛出。
|
|
/// </exception>
|
|
public GodotYamlConfigTableSource(
|
|
string tableName,
|
|
string configRelativePath,
|
|
string? schemaRelativePath = null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(tableName))
|
|
{
|
|
throw new ArgumentException("Table name cannot be null or whitespace.", nameof(tableName));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(configRelativePath))
|
|
{
|
|
throw new ArgumentException("Config relative path cannot be null or whitespace.",
|
|
nameof(configRelativePath));
|
|
}
|
|
|
|
if (!IsSafeRelativePath(configRelativePath))
|
|
{
|
|
throw new ArgumentException(
|
|
"Config relative path must be a safe relative path without root segments or traversal markers.",
|
|
nameof(configRelativePath));
|
|
}
|
|
|
|
if (schemaRelativePath != null && string.IsNullOrWhiteSpace(schemaRelativePath))
|
|
{
|
|
throw new ArgumentException(
|
|
"Schema relative path cannot be empty or whitespace when provided.",
|
|
nameof(schemaRelativePath));
|
|
}
|
|
|
|
if (schemaRelativePath != null && !IsSafeRelativePath(schemaRelativePath))
|
|
{
|
|
throw new ArgumentException(
|
|
"Schema relative path must be a safe relative path without root segments or traversal markers.",
|
|
nameof(schemaRelativePath));
|
|
}
|
|
|
|
TableName = tableName;
|
|
ConfigRelativePath = configRelativePath;
|
|
SchemaRelativePath = schemaRelativePath;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取运行时表名称。
|
|
/// </summary>
|
|
public string TableName { get; }
|
|
|
|
/// <summary>
|
|
/// 获取相对配置根目录的 YAML 目录路径。
|
|
/// 该值始终保持为无根相对路径,不会包含 <c>.</c>、<c>..</c> 或 <c>:</c> 段。
|
|
/// </summary>
|
|
public string ConfigRelativePath { get; }
|
|
|
|
/// <summary>
|
|
/// 获取相对配置根目录的 schema 文件路径;未启用 schema 校验时为空。
|
|
/// 该值在非空时始终保持为无根相对路径,不会包含 <c>.</c>、<c>..</c> 或 <c>:</c> 段。
|
|
/// </summary>
|
|
public string? SchemaRelativePath { get; }
|
|
|
|
private static bool IsSafeRelativePath(string path)
|
|
{
|
|
var normalizedPath = path.Replace('\\', '/');
|
|
if (normalizedPath.StartsWith("/", StringComparison.Ordinal) ||
|
|
normalizedPath.StartsWith("res://", StringComparison.Ordinal) ||
|
|
normalizedPath.StartsWith("user://", StringComparison.Ordinal) ||
|
|
Path.IsPathRooted(path) ||
|
|
HasWindowsDrivePrefix(normalizedPath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (normalizedPath.Contains(':', StringComparison.Ordinal))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var segment in normalizedPath.Split('/', StringSplitOptions.RemoveEmptyEntries))
|
|
{
|
|
if (segment is "." or "..")
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool HasWindowsDrivePrefix(string path)
|
|
{
|
|
return path.Length >= 2 &&
|
|
char.IsLetter(path[0]) &&
|
|
path[1] == ':';
|
|
}
|
|
}
|