diff --git a/TIASshot.Tests/IoUtilCreateSaveFolderTests.cs b/TIASshot.Tests/IoUtilCreateSaveFolderTests.cs new file mode 100644 index 0000000..c0c1627 --- /dev/null +++ b/TIASshot.Tests/IoUtilCreateSaveFolderTests.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; +using NUnit.Framework; + +namespace TIASshot.Tests { + /// + /// IoUtil.CreateSaveFolder のテスト. + /// データ名(ユーザー入力)がフォルダ名要素として無害化され, + /// 保存先が設定ベースフォルダ(File/SaveFolder)の外へ出ないことを検証する. + /// + [TestFixture] + public class IoUtilCreateSaveFolderTests { + + private string _rootDir; // テスト用ルート(脱出検知用の外側) + private string _baseSaveFolder; // config の File/SaveFolder に設定するベース + private string _tempConfigPath; + + [SetUp] + public void SetUp() { + // 脱出をルート内に閉じ込めて観測できるよう,ベースを深い位置に置く + _rootDir = Path.Combine(Path.GetTempPath(), "TIASshotTests_" + Guid.NewGuid().ToString("N")); + _baseSaveFolder = Path.Combine(_rootDir, "outer", "SaveFolder"); + Directory.CreateDirectory(_baseSaveFolder); + + _tempConfigPath = Path.Combine(_rootDir, "config.xml"); + var xml = + "" + + "" + _baseSaveFolder + ""; + File.WriteAllText(_tempConfigPath, xml, System.Text.Encoding.UTF8); + Config.Load(_tempConfigPath); + } + + [TearDown] + public void TearDown() { + if (Directory.Exists(_rootDir)) Directory.Delete(_rootDir, true); + } + + /// + /// 生成されたフォルダがベースフォルダ配下かどうかを判定する + /// + private bool IsUnderBase(string path) { + var full = Path.GetFullPath(path); + var baseFull = Path.GetFullPath(_baseSaveFolder) + .TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; + return full.StartsWith(baseFull, StringComparison.OrdinalIgnoreCase); + } + + /// + /// データ名に ..\ トラバーサルを含めても保存先がベースフォルダの外へ出ない + /// + [Test] + public void CreateSaveFolder_トラバーサル入力_ベースフォルダ配下に留まる() { + // Arrange: ".." 4 個で SaveFolder\日付\時刻- の 3 段を超えて脱出を試みる入力 + var note = @"..\..\..\..\evil"; + + // Act + var result = IoUtil.CreateSaveFolder(note); + + // Assert + Assert.IsTrue(IsUnderBase(result), + $"保存フォルダがベースフォルダの外に生成された: {Path.GetFullPath(result)}"); + Assert.IsFalse(Directory.Exists(Path.Combine(_rootDir, "evil")), + "ベースフォルダ外に 'evil' ディレクトリが作られてはならない"); + } + + /// + /// データ名に不正なファイル名文字(: ? 等)を含めても例外にならず,配下に生成される + /// + [Test] + public void CreateSaveFolder_不正文字入力_例外を出さず配下に生成() { + // Arrange + var note = "a:b?c"; + + // Act + string result = null; + Assert.DoesNotThrow(() => { result = IoUtil.CreateSaveFolder(note); }, + "不正文字を含むデータ名で例外が発生してはならない(撮影中断の可用性影響)"); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(Directory.Exists(result), "保存フォルダが生成されていること"); + Assert.IsTrue(IsUnderBase(result), "保存フォルダはベースフォルダ配下であること"); + } + + /// + /// 通常のデータ名(安全な文字のみ)はそのままフォルダ名末尾に使われる(現状挙動の固定) + /// + [Test] + public void CreateSaveFolder_通常入力_データ名がフォルダ名に保持される() { + // Arrange + var note = "subject01"; + + // Act + var result = IoUtil.CreateSaveFolder(note); + + // Assert + Assert.IsTrue(Directory.Exists(result), "保存フォルダが生成されていること"); + Assert.IsTrue(IsUnderBase(result), "保存フォルダはベースフォルダ配下であること"); + StringAssert.EndsWith("-subject01", Path.GetFileName(result), + "フォルダ名末尾にデータ名がそのまま保持されること"); + } + } +} diff --git a/TIASshot.Tests/TIASshot.Tests.csproj b/TIASshot.Tests/TIASshot.Tests.csproj index 829bc8b..6cf0344 100644 --- a/TIASshot.Tests/TIASshot.Tests.csproj +++ b/TIASshot.Tests/TIASshot.Tests.csproj @@ -59,6 +59,7 @@ +