using System.Runtime.InteropServices; namespace MBN_STOCK_WEBVIEW.Playout.Tests; public sealed class TrustedPlayoutAssetPathTests { private static readonly IReadOnlySet Allowed = new HashSet( [".vrv", ".png"], StringComparer.OrdinalIgnoreCase); [Fact] public void ResolveRelativeFile_AcceptsOneInternalRegularFile() { using var root = TemporarySceneDirectory.Create("studio.vrv"); var resolved = TrustedPlayoutAssetPath.ResolveRelativeFile( root.Path, "studio.vrv", Allowed); Assert.Equal(Path.Combine(root.Path, "studio.vrv"), resolved, ignoreCase: true); } [Theory] [InlineData("..\\outside.vrv")] [InlineData("C:\\outside.vrv")] public void ResolveRelativeFile_RejectsRootEscape(string input) { using var root = TemporarySceneDirectory.Create("studio.vrv"); Assert.Throws(() => TrustedPlayoutAssetPath.ResolveRelativeFile(root.Path, input, Allowed)); } [Fact] public void ResolveRelativeFile_RejectsMissingFile() { using var root = TemporarySceneDirectory.Create(); Assert.Throws(() => TrustedPlayoutAssetPath.ResolveRelativeFile(root.Path, "missing.vrv", Allowed)); } [Fact] public void ResolveAbsoluteFile_RejectsPickerSelectionOutsideTheRoot() { using var root = TemporarySceneDirectory.Create(); using var outside = TemporarySceneDirectory.Create("outside.vrv"); Assert.Throws(() => TrustedPlayoutAssetPath.ResolveAbsoluteFile( root.Path, Path.Combine(outside.Path, "outside.vrv"), Allowed)); } [Fact] public void ResolveRelativeFile_RejectsHardLinkedFile() { using var root = TemporarySceneDirectory.Create(); var parent = Directory.GetParent(root.Path)!.FullName; var outside = Path.Combine(parent, "outside.vrv"); File.WriteAllText(outside, "outside trusted-looking bytes"); var link = Path.Combine(root.Path, "linked.vrv"); Assert.True(CreateHardLinkW(link, outside, IntPtr.Zero)); Assert.Throws(() => TrustedPlayoutAssetPath.ResolveRelativeFile(root.Path, "linked.vrv", Allowed)); } [Fact] public void ResolveRelativeFile_RejectsReparseDirectory() { using var root = TemporarySceneDirectory.Create(); var parent = Directory.GetParent(root.Path)!.FullName; var outside = Path.Combine(parent, "outside"); Directory.CreateDirectory(outside); File.WriteAllText(Path.Combine(outside, "studio.vrv"), "outside bytes"); var link = Path.Combine(root.Path, "linked"); try { Directory.CreateSymbolicLink(link, outside); } catch (Exception exception) when ( exception is UnauthorizedAccessException or IOException or PlatformNotSupportedException) { throw Xunit.Sdk.SkipException.ForSkip( "The Windows test host does not permit creating a symbolic link."); } Assert.Throws(() => TrustedPlayoutAssetPath.ResolveRelativeFile( root.Path, "linked\\studio.vrv", Allowed)); } [Fact] public void EnsureOpenedRegularFile_BindsTheHandleToTheExpectedPath() { using var root = TemporarySceneDirectory.Create("expected.vrv", "other.vrv"); var expected = Path.Combine(root.Path, "expected.vrv"); var other = Path.Combine(root.Path, "other.vrv"); using var stream = new FileStream( other, FileMode.Open, FileAccess.Read, FileShare.Read); Assert.Throws(() => TrustedPlayoutAssetPath.EnsureOpenedRegularFile( stream.SafeFileHandle, expected)); } [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CreateHardLinkW( string fileName, string existingFileName, IntPtr securityAttributes); }