diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModCompDependency.cs b/Plain Craft Launcher 2/Modules/Minecraft/ModCompDependency.cs index a6188b09b..af629304c 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModCompDependency.cs +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModCompDependency.cs @@ -202,13 +202,52 @@ public static List BuildDependencyDownloads( continue; } - var targetPath = Path.Combine(targetModsFolder ?? string.Empty, ModComp.CompFileNameGet(depProject, depCompFile)); + var targetPath = BuildSafeDependencyTargetPath(targetModsFolder, ModComp.CompFileNameGet(depProject, depCompFile)); downloads.Add(depCompFile.ToNetFile(targetPath)); } return downloads; } + private static string BuildSafeDependencyTargetPath(string targetModsFolder, string fileName) + { + if (string.IsNullOrWhiteSpace(targetModsFolder)) + { + throw new IOException("依赖下载路径无效"); + } + + var safeFileName = SanitizeDependencyFileName(fileName); + var targetRoot = Path.GetFullPath(targetModsFolder); + var targetPath = Path.GetFullPath(Path.Combine(targetRoot, safeFileName)); + var normalizedRoot = targetRoot.EndsWith(Path.DirectorySeparatorChar) || targetRoot.EndsWith(Path.AltDirectorySeparatorChar) + ? targetRoot + : targetRoot + Path.DirectorySeparatorChar; + + if (!targetPath.StartsWith(normalizedRoot, StringComparison.OrdinalIgnoreCase)) + { + throw new IOException("依赖下载路径无效"); + } + + return targetPath; + } + + private static string SanitizeDependencyFileName(string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + return "dependency.jar"; + } + + var invalidChars = Path.GetInvalidFileNameChars() + .Concat(new[] { '/', '\\', ':', '*', '?', '"', '<', '>', '|' }) + .ToHashSet(); + var safeChars = fileName + .Select(ch => invalidChars.Contains(ch) || char.IsControl(ch) ? '_' : ch) + .ToArray(); + var safeFileName = new string(safeChars).Trim(); + return string.IsNullOrWhiteSpace(safeFileName) ? "dependency.jar" : safeFileName; + } + /// /// Shows confirmation dialog for required dependency installs. /// Returns true if user confirms, false if user cancels or there are unresolved required deps.