using AIStudio.Tools; namespace AIStudio.Tests.Tools; /// /// Checks the names of ZIP entries before anything is extracted. /// /// /// A plugin archive, the configuration of an organization, or a backup comes from somewhere else. A /// name which leads outside of the target directory would let it overwrite any file the user may /// write, the settings among them. /// [TestFixture] public sealed class ZipEntryNamesTests { [TestCase("plugin/plugin.lua", "plugin/plugin.lua")] [TestCase("plugin\\plugin.lua", "plugin/plugin.lua")] [TestCase("data/workspaces/", "data/workspaces/")] public void ASafeNameIsKeptWithForwardSlashes(string entryName, string expected) { Assert.That(ZipEntryNames.Normalize(entryName), Is.EqualTo(expected)); } [TestCase("")] [TestCase("/etc/passwd")] [TestCase("\\Windows\\win.ini")] [TestCase("C:/Users/settings.json")] [TestCase("../settings.json")] [TestCase("data/../../settings.json")] [TestCase("data\\..\\..\\settings.json")] [TestCase("./plugin.lua")] [TestCase("plugin\0.lua")] public void ANameWhichCouldLeadElsewhereIsRejected(string entryName) { Assert.Multiple(() => { Assert.That(() => ZipEntryNames.Normalize(entryName), Throws.InstanceOf()); Assert.That(ZipEntryNames.TryNormalize(entryName, out var normalizedEntryName), Is.False); Assert.That(normalizedEntryName, Is.Empty); }); } [Test] public void ASafeNameLiesBelowTheTargetDirectory() { var targetDirectory = Path.Combine(Path.GetTempPath(), "ai-studio-zip-target"); var destinationPath = ZipEntryNames.GetDestinationPath(targetDirectory, ZipEntryNames.Normalize("attachments/transcripts/meeting.md")); Assert.That(destinationPath, Is.EqualTo(Path.Combine(Path.GetFullPath(targetDirectory), "attachments", "transcripts", "meeting.md"))); } }