93 lines
3.8 KiB
C#
93 lines
3.8 KiB
C#
using System;
|
|
using System.IO;
|
|
using Paca.Core.Platform;
|
|
using Paca.Core.Util;
|
|
using Xunit;
|
|
|
|
namespace Paca.Tests.Core;
|
|
|
|
/// <summary>
|
|
/// Cycle 1: 극한 RED 시나리오 - 파일명 정제 및 윈도우 장치 예약어, 트로이목마 Bidi 스푸핑, 경로 누수 방어 검증
|
|
/// </summary>
|
|
public class FileNameUtilAndCleanupExtremeRedTests
|
|
{
|
|
[Theory]
|
|
[InlineData("CON_long_stream_title", 3, "_CON")]
|
|
[InlineData("PRN_document_capture", 3, "_PRN")]
|
|
[InlineData("AUX_recording_session", 3, "_AUX")]
|
|
[InlineData("NUL_blackhole_stream", 3, "_NUL")]
|
|
[InlineData("COM1_serial_diagnostic", 4, "_COM1")]
|
|
[InlineData("LPT1_parallel_port", 4, "_LPT1")]
|
|
[InlineData("con.something.video", 3, "_con")]
|
|
public void Cycle1_Gate1_TruncatedToReservedName_MustPrependUnderscore(string input, int maxLen, string expectedPrefix)
|
|
{
|
|
// Act
|
|
var sanitized = FileNameUtil.Sanitize(input, maxLen);
|
|
|
|
// Assert: 윈도우 장치 예약어(CON, PRN, AUX, NUL, COM1-9, LPT1-9)와 일치해서는 안 됨
|
|
Assert.StartsWith(expectedPrefix, sanitized, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotMatch(@"^(?i)(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$", sanitized);
|
|
}
|
|
|
|
[Fact]
|
|
public void Cycle1_Gate2_BidiTrojanSourceAndZeroWidth_MustBeSanitized()
|
|
{
|
|
// CVE 클래스: Trojan Source / Unicode Bidi Spoofing
|
|
// \u202E (Right-to-Left Override): "video\u202E4pm.exe" -> 화면에는 "videoexe.mp4" 로 착시 유도
|
|
var trojanBidi = "my_video\u202E4pm.exe";
|
|
var sanitizedBidi = FileNameUtil.Sanitize(trojanBidi);
|
|
|
|
Assert.DoesNotContain('\u202E', sanitizedBidi);
|
|
Assert.DoesNotContain('\u202A', sanitizedBidi);
|
|
Assert.DoesNotContain('\u202B', sanitizedBidi);
|
|
Assert.DoesNotContain('\u202C', sanitizedBidi);
|
|
Assert.DoesNotContain('\u202D', sanitizedBidi);
|
|
|
|
// 제로 너비 공백(\u200B), BOM(\uFEFF) 등 인비저블 캐릭터 제거
|
|
var zeroWidth = "video\u200B\u200C\u200D\uFEFFtitle";
|
|
var sanitizedZero = FileNameUtil.Sanitize(zeroWidth);
|
|
Assert.DoesNotContain('\u200B', sanitizedZero);
|
|
Assert.DoesNotContain('\u200C', sanitizedZero);
|
|
Assert.DoesNotContain('\u200D', sanitizedZero);
|
|
Assert.DoesNotContain('\uFEFF', sanitizedZero);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("............", 5, "video")]
|
|
[InlineData(" ", 8, "video")]
|
|
[InlineData("valid_name", 0, "video")]
|
|
[InlineData("valid_name", -5, "video")]
|
|
[InlineData("...", 1, "video")]
|
|
public void Cycle1_Gate3_DegenerateMaxLenAndDotSpaceCollapse_MustFallbackToVideo(string input, int maxLen, string expected)
|
|
{
|
|
// Act
|
|
var result = FileNameUtil.Sanitize(input, maxLen);
|
|
|
|
// Assert: 빈 문자열이나 ArgumentOutOfRangeException 없이 안전한 기본값 반환
|
|
Assert.Equal(expected, result);
|
|
}
|
|
|
|
[Fact]
|
|
public void Cycle1_Gate4_DownloadMetaPathResolver_TempAndParts_MustBeInPacaHiddenDirectory()
|
|
{
|
|
var tempRoot = Path.Combine(Path.GetTempPath(), "paca_test_" + Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(tempRoot);
|
|
try
|
|
{
|
|
var outputBase = Path.Combine(tempRoot, "sample_video");
|
|
|
|
var partsDir = DownloadMetaPathResolver.GetPartsDirectory(outputBase);
|
|
var tempPartFile = DownloadMetaPathResolver.GetTempPartFilePath(outputBase);
|
|
|
|
// .paca 격리 서브디렉터리 내부에 생성되어야 함
|
|
Assert.Contains(".paca", partsDir);
|
|
Assert.Contains(".paca", tempPartFile);
|
|
Assert.EndsWith("sample_video.parts", partsDir);
|
|
Assert.EndsWith("sample_video.part", tempPartFile);
|
|
}
|
|
finally
|
|
{
|
|
try { if (Directory.Exists(tempRoot)) Directory.Delete(tempRoot, true); } catch { }
|
|
}
|
|
}
|
|
}
|