Newer
Older
TIASShot / TIASshot.Tests / IoUtilCreateSaveFolderTests.cs
using System;
using System.IO;
using NUnit.Framework;

namespace TIASshot.Tests {
    /// <summary>
    /// IoUtil.CreateSaveFolder のテスト.
    /// データ名(ユーザー入力)がフォルダ名要素として無害化され,
    /// 保存先が設定ベースフォルダ(File/SaveFolder)の外へ出ないことを検証する.
    /// </summary>
    [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 =
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
                "<Config><File><SaveFolder>" + _baseSaveFolder + "</SaveFolder></File></Config>";
            File.WriteAllText(_tempConfigPath, xml, System.Text.Encoding.UTF8);
            Config.Load(_tempConfigPath);
        }

        [TearDown]
        public void TearDown() {
            if (Directory.Exists(_rootDir)) Directory.Delete(_rootDir, true);
        }

        /// <summary>
        /// 生成されたフォルダがベースフォルダ配下かどうかを判定する
        /// </summary>
        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);
        }

        /// <summary>
        /// データ名に ..\ トラバーサルを含めても保存先がベースフォルダの外へ出ない
        /// </summary>
        [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' ディレクトリが作られてはならない");
        }

        /// <summary>
        /// データ名に不正なファイル名文字(: ? 等)を含めても例外にならず,配下に生成される
        /// </summary>
        [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), "保存フォルダはベースフォルダ配下であること");
        }

        /// <summary>
        /// 通常のデータ名(安全な文字のみ)はそのままフォルダ名末尾に使われる(現状挙動の固定)
        /// </summary>
        [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),
                "フォルダ名末尾にデータ名がそのまま保持されること");
        }
    }
}