Files
douyin/storage/SafeLocalMigrationFile.cs

71 lines
2.5 KiB
C#

namespace dy.net.storage
{
public static class SafeLocalMigrationFile
{
public static bool TryResolve(string path, IEnumerable<string> allowedRoots, out string fullPath, out string error)
{
fullPath = null;
error = null;
if (string.IsNullOrWhiteSpace(path))
{
error = "本地路径为空";
return false;
}
try { fullPath = Path.GetFullPath(path); }
catch (Exception ex)
{
error = "本地路径无效:" + ex.Message;
return false;
}
var resolvedPath = fullPath;
var root = (allowedRoots ?? Array.Empty<string>())
.Select(Path.GetFullPath)
.Where(candidate => IsWithin(resolvedPath, candidate))
.OrderByDescending(x => x.Length)
.FirstOrDefault();
if (root == null)
{
error = "文件不在账号配置的旧存储根目录内";
return false;
}
if (!File.Exists(fullPath))
{
error = "本地文件不存在";
return false;
}
try
{
for (var current = fullPath; !string.IsNullOrWhiteSpace(current); current = Path.GetDirectoryName(current))
{
FileSystemInfo info = File.Exists(current) ? new FileInfo(current) : new DirectoryInfo(current);
if (!string.IsNullOrWhiteSpace(info.LinkTarget))
{
error = "旧文件路径包含符号链接,已拒绝迁移";
return false;
}
if (string.Equals(current, root, StringComparison.Ordinal)) break;
}
}
catch (Exception ex)
{
error = "无法验证旧文件路径:" + ex.Message;
return false;
}
return true;
}
public static bool IsWithin(string candidate, string root)
{
var fullCandidate = Path.GetFullPath(candidate).TrimEnd(Path.DirectorySeparatorChar);
var fullRoot = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar);
return string.Equals(fullCandidate, fullRoot, StringComparison.Ordinal)
|| fullCandidate.StartsWith(fullRoot + Path.DirectorySeparatorChar, StringComparison.Ordinal);
}
}
}