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.That(IsUnderBase(result), Is.True,
$"保存フォルダがベースフォルダの外に生成された: {Path.GetFullPath(result)}");
Assert.That(Directory.Exists(Path.Combine(_rootDir, "evil")), Is.False,
"ベースフォルダ外に '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.That(result, Is.Not.Null);
Assert.That(Directory.Exists(result), Is.True, "保存フォルダが生成されていること");
Assert.That(IsUnderBase(result), Is.True, "保存フォルダはベースフォルダ配下であること");
}
/// <summary>
/// 通常のデータ名(安全な文字のみ)はそのままフォルダ名末尾に使われる(現状挙動の固定)
/// </summary>
[Test]
public void CreateSaveFolder_通常入力_データ名がフォルダ名に保持される() {
// Arrange
var note = "subject01";
// Act
var result = IoUtil.CreateSaveFolder(note);
// Assert
Assert.That(Directory.Exists(result), Is.True, "保存フォルダが生成されていること");
Assert.That(IsUnderBase(result), Is.True, "保存フォルダはベースフォルダ配下であること");
Assert.That(Path.GetFileName(result), Does.EndWith("-subject01"),
"フォルダ名末尾にデータ名がそのまま保持されること");
}
}
}