From bc11d7ceafe88994ce1ac8c2021ecca72b308ad0 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 9 Feb 2020 00:25:21 -0700 Subject: [PATCH] Implement Nintendo's path normalization functions --- src/LibHac/Common/U8Span.cs | 24 +- src/LibHac/Common/U8String.cs | 2 +- src/LibHac/Fs/FileSystemClient.cs | 2 +- src/LibHac/Fs/MountHelpers.cs | 4 +- src/LibHac/Fs/PathTool.cs | 731 ++++++++++++++++ src/LibHac/Fs/PathUtility.cs | 32 + src/LibHac/Fs/StringTraits.cs | 11 + src/LibHac/FsService/PathNormalizer.cs | 45 + src/LibHac/FsSystem/PathTools.cs | 9 +- tests/LibHac.Tests/DebugAssertHandler.cs | 12 + .../LibHac.Tests/Fs/PathToolTestGenerator.cpp | 279 ++++++ tests/LibHac.Tests/Fs/PathToolTests.cs | 817 ++++++++++++++++++ tests/LibHac.Tests/LibHacTestFramework.cs | 30 + tests/LibHac.Tests/ResultNameResolver.cs | 36 + 14 files changed, 2025 insertions(+), 9 deletions(-) create mode 100644 src/LibHac/Fs/PathTool.cs create mode 100644 src/LibHac/Fs/PathUtility.cs create mode 100644 src/LibHac/Fs/StringTraits.cs create mode 100644 src/LibHac/FsService/PathNormalizer.cs create mode 100644 tests/LibHac.Tests/DebugAssertHandler.cs create mode 100644 tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp create mode 100644 tests/LibHac.Tests/Fs/PathToolTests.cs create mode 100644 tests/LibHac.Tests/LibHacTestFramework.cs create mode 100644 tests/LibHac.Tests/ResultNameResolver.cs diff --git a/src/LibHac/Common/U8Span.cs b/src/LibHac/Common/U8Span.cs index 3e46ab0d..03705d98 100644 --- a/src/LibHac/Common/U8Span.cs +++ b/src/LibHac/Common/U8Span.cs @@ -1,5 +1,7 @@ using System; using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; namespace LibHac.Common @@ -27,6 +29,26 @@ namespace LibHac.Common _buffer = Encoding.UTF8.GetBytes(value); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetOrNull(int i) + { + byte value = 0; + ReadOnlySpan b = _buffer; + + if ((uint)i < (uint)b.Length) + { + value = b[i]; + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetUnsafe(int i) + { + return Unsafe.Add(ref MemoryMarshal.GetReference(_buffer), i); + } + public U8Span Slice(int start) { return new U8Span(_buffer.Slice(start)); @@ -52,6 +74,6 @@ namespace LibHac.Common return new U8String(_buffer.ToArray()); } - public bool IsNull() => _buffer == default; + public bool IsEmpty() => _buffer.IsEmpty; } } diff --git a/src/LibHac/Common/U8String.cs b/src/LibHac/Common/U8String.cs index e7c1552f..e995298e 100644 --- a/src/LibHac/Common/U8String.cs +++ b/src/LibHac/Common/U8String.cs @@ -46,6 +46,6 @@ namespace LibHac.Common return StringUtils.Utf8ZToString(_buffer); } - public bool IsNull() => _buffer == null; + public bool IsEmpty() => _buffer == null || _buffer.Length == 0; } } diff --git a/src/LibHac/Fs/FileSystemClient.cs b/src/LibHac/Fs/FileSystemClient.cs index e779b749..a80bd712 100644 --- a/src/LibHac/Fs/FileSystemClient.cs +++ b/src/LibHac/Fs/FileSystemClient.cs @@ -113,7 +113,7 @@ namespace LibHac.Fs internal static Result GetMountName(ReadOnlySpan path, out ReadOnlySpan mountName, out ReadOnlySpan subPath) { int mountLen = 0; - int maxMountLen = Math.Min(path.Length, PathTools.MountNameLength); + int maxMountLen = Math.Min(path.Length, PathTools.MountNameLengthMax); for (int i = 0; i <= maxMountLen; i++) { diff --git a/src/LibHac/Fs/MountHelpers.cs b/src/LibHac/Fs/MountHelpers.cs index 33ecb09e..e2ad4312 100644 --- a/src/LibHac/Fs/MountHelpers.cs +++ b/src/LibHac/Fs/MountHelpers.cs @@ -6,7 +6,7 @@ namespace LibHac.Fs { public static Result CheckMountName(U8Span name) { - if (name.IsNull()) return ResultFs.NullArgument.Log(); + if (name.IsEmpty()) return ResultFs.NullArgument.Log(); if (name.Length > 0 && name[0] == '@') return ResultFs.InvalidMountName.Log(); if (!CheckMountNameImpl(name)) return ResultFs.InvalidMountName.Log(); @@ -16,7 +16,7 @@ namespace LibHac.Fs public static Result CheckMountNameAcceptingReservedMountName(U8Span name) { - if (name.IsNull()) return ResultFs.NullArgument.Log(); + if (name.IsEmpty()) return ResultFs.NullArgument.Log(); if (!CheckMountNameImpl(name)) return ResultFs.InvalidMountName.Log(); diff --git a/src/LibHac/Fs/PathTool.cs b/src/LibHac/Fs/PathTool.cs new file mode 100644 index 00000000..d7d511bd --- /dev/null +++ b/src/LibHac/Fs/PathTool.cs @@ -0,0 +1,731 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibHac.Common; +using LibHac.FsSystem; + +namespace LibHac.Fs +{ + public static class PathTool + { + public static bool IsSeparator(byte c) + { + return c == StringTraits.DirectorySeparator; + } + + public static bool IsAltSeparator(byte c) + { + return c == StringTraits.AltDirectorySeparator; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAnySeparator(byte c) + { + return IsSeparator(c) || IsAltSeparator(c); + } + + public static bool IsNullTerminator(byte c) + { + return c == StringTraits.NullTerminator; + } + + public static bool IsDot(byte c) + { + return c == StringTraits.Dot; + } + + public static bool IsDriveSeparator(byte c) + { + return c == StringTraits.DriveSeparator; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsCurrentDirectory(ReadOnlySpan p) + { + if ((uint)p.Length < 1) return false; + + ref byte b = ref MemoryMarshal.GetReference(p); + + return IsDot(b) && (p.Length == 1 || IsSeparator(Unsafe.Add(ref b, 1)) || IsNullTerminator(Unsafe.Add(ref b, 1))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsParentDirectory(ReadOnlySpan p) + { + if ((uint)p.Length < 2) return false; + + ref byte b = ref MemoryMarshal.GetReference(p); + + return IsDot(b) && IsDot(Unsafe.Add(ref b, 1)) && + (p.Length == 2 || IsSeparator(Unsafe.Add(ref b, 2)) || IsNullTerminator(Unsafe.Add(ref b, 2))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsParentDirectoryAlt(ReadOnlySpan p) + { + if ((uint)p.Length < 3) return false; + + ref byte b = ref MemoryMarshal.GetReference(p); + + return IsAnySeparator(b) && IsDot(Unsafe.Add(ref b, 1)) && IsDot(Unsafe.Add(ref b, 2)) && + (p.Length == 3 || IsAnySeparator(Unsafe.Add(ref b, 3)) || IsNullTerminator(Unsafe.Add(ref b, 3))); + } + + /// + /// Checks if a path begins with / or \ and contains any of these patterns: + /// "/..\", "\..\", "\../", "\..0" where '0' is the null terminator. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ContainsParentDirectoryAlt(U8Span path2) + { + if (!IsAnySeparator(path2.GetOrNull(0))) return false; + + for (int i = 0; i < path2.Length - 2; i++) + { + byte c = path2[i]; + + if (IsSeparator(c) && + IsDot(path2[i + 1]) && + IsDot(path2[i + 2]) && + IsAltSeparator(path2.GetOrNull(i + 3))) + { + return true; + } + + if (IsAltSeparator(c) && + IsDot(path2[i + 1]) && + IsDot(path2[i + 2])) + { + byte c3 = path2.GetOrNull(i + 3); + + if (IsNullTerminator(c3) || IsAnySeparator(c3)) + { + return true; + } + } + else if (IsNullTerminator(c)) + { + return false; + } + } + + return false; + } + + public static Result Normalize(Span outputBuffer, out long normalizedLength, U8Span path, + bool preserveUnc, bool hasMountName) + { + normalizedLength = default; + + U8Span path2 = path; + int prefixLength = 0; + bool isUncPath = false; + + if (hasMountName) + { + Result rc = ParseMountName(out path2, outputBuffer, out long mountNameLength, path2); + if (rc.IsFailure()) return rc; + + prefixLength += (int)mountNameLength; + } + + if (preserveUnc) + { + U8Span originalPath = path2; + + Result rc = ParseWindowsPath(out path2, outputBuffer.Slice(prefixLength), out long windowsPathLength, + out _, false, path2, hasMountName); + if (rc.IsFailure()) return rc; + + prefixLength += (int)windowsPathLength; + if (originalPath.Value != path2.Value) + { + isUncPath = true; + } + } + + if (prefixLength == 0 && !IsSeparator(path2.GetOrNull(0))) + return ResultFs.InvalidPathFormat.Log(); + + if (ContainsParentDirectoryAlt(path2)) + { + var buffer2 = new byte[PathTools.MaxPathLength + 1]; + + buffer2[0] = StringTraits.DirectorySeparator; + int j; + + for (j = 1; j < path2.Length; j++) + { + byte c = path2[j]; + if (IsNullTerminator(c)) + break; + + // Current char is a dot. Check the surrounding chars for the /../ pattern + if (IsDot(c) && IsParentDirectoryAlt(path2.Value.Slice(j - 1))) + { + buffer2[j - 1] = StringTraits.DirectorySeparator; + buffer2[j] = StringTraits.Dot; + buffer2[j + 1] = StringTraits.Dot; + + j += 2; + + if (!IsNullTerminator(path2.GetOrNull(j))) + { + buffer2[j] = StringTraits.DirectorySeparator; + } + } + else + { + buffer2[j] = c; + } + } + + buffer2[j] = StringTraits.NullTerminator; + path2 = new U8Span(buffer2); + } + + int i = 0; + bool skipNextSep = false; + int totalLength = prefixLength; + + while (i < path2.Length && !IsNullTerminator(path2[i])) + { + if (IsSeparator(path2[i])) + { + do + { + i++; + } while (i < path2.Length && IsSeparator(path2[i])); + + if (i >= path2.Length || IsNullTerminator(path2[i])) + { + break; + } + + if (!skipNextSep) + { + if (totalLength + 1 == outputBuffer.Length) + { + outputBuffer[totalLength] = StringTraits.NullTerminator; + normalizedLength = totalLength; + + return ResultFs.TooLongPath.Log(); + } + + outputBuffer[totalLength++] = StringTraits.DirectorySeparator; + } + + skipNextSep = false; + } + + int dirLen = 0; + while (path2.Length > i + dirLen && !IsNullTerminator(path2[i + dirLen]) && !IsSeparator(path2[i + dirLen])) + { + dirLen++; + } + + if (IsCurrentDirectory(path2.Slice(i))) + { + skipNextSep = true; + } + else if (IsParentDirectory(path2.Slice(i))) + { + Debug.Assert(IsSeparator(outputBuffer[totalLength - 1])); + Debug.Assert(IsSeparator(outputBuffer[prefixLength])); + + if (totalLength == prefixLength + 1) + { + if (isUncPath) + { + totalLength--; + } + else + { + return ResultFs.DirectoryUnobtainable.Log(); + } + } + else + { + totalLength -= 2; + while (!IsSeparator(outputBuffer[totalLength])) + { + totalLength--; + + if (totalLength == prefixLength) + { + break; + } + } + } + + Debug.Assert(IsSeparator(outputBuffer[totalLength])); + Debug.Assert(totalLength < outputBuffer.Length); + } + else + { + if (totalLength + dirLen + 1 <= outputBuffer.Length) + { + for (int j = 0; j < dirLen; j++) + { + outputBuffer[totalLength++] = path2[i + j]; + } + } + else + { + int copyLen = outputBuffer.Length - 1 - totalLength; + + for (int j = 0; j < copyLen; j++) + { + outputBuffer[totalLength++] = path2[i + j]; + } + + outputBuffer[totalLength] = StringTraits.NullTerminator; + normalizedLength = totalLength; + return ResultFs.TooLongPath.Log(); + } + } + + i += dirLen; + } + + if (skipNextSep) + totalLength--; + + if (totalLength < outputBuffer.Length && totalLength == prefixLength && !isUncPath) + { + outputBuffer[prefixLength] = StringTraits.DirectorySeparator; + totalLength++; + } + + if (totalLength - 1 > outputBuffer.Length) + { + return ResultFs.TooLongPath.Log(); + } + + outputBuffer[totalLength] = StringTraits.NullTerminator; + + Debug.Assert(IsNormalized(out bool isNormalized, new U8Span(outputBuffer), preserveUnc, hasMountName).IsSuccess()); + Debug.Assert(isNormalized); + + normalizedLength = totalLength; + return Result.Success; + } + + public static Result IsNormalized(out bool isNormalized, U8Span path, bool preserveUnc, bool hasMountName) + { + isNormalized = default; + U8Span path2 = path; + bool isUncPath = false; + + if (hasMountName) + { + Result rc = ParseMountName(out path2, Span.Empty, out _, path); + if (rc.IsFailure()) return rc; + + if (path2.Length == 0 || !IsSeparator(path2[0])) + return ResultFs.InvalidPathFormat.Log(); + } + + if (preserveUnc) + { + U8Span originalPath = path2; + + Result rc = ParseWindowsPath(out path2, Span.Empty, out _, out bool isNormalizedWin, + true, originalPath, hasMountName); + if (rc.IsFailure()) return rc; + + if (!isNormalizedWin) + { + isNormalized = false; + return Result.Success; + } + + // Path is a UNC path if the new path skips part of the original + isUncPath = originalPath.Value != path2.Value; + + if (isUncPath) + { + if (IsSeparator(originalPath.GetOrNull(0)) && IsSeparator(originalPath.GetOrNull(1))) + { + isNormalized = false; + return Result.Success; + } + + if (IsNullTerminator(path2.GetOrNull(0))) + { + isNormalized = true; + return Result.Success; + } + } + } + + if (path2.IsEmpty() || IsNullTerminator(path2[0])) + return ResultFs.InvalidPathFormat.Log(); + + if (ContainsParentDirectoryAlt(path2)) + { + isNormalized = false; + return Result.Success; + } + + bool pathWasSkipped = path.Value != path2.Value; + var state = NormalizeState.Initial; + + for (int i = 0; i < path2.Length; i++) + { + byte c = path2[i]; + if (IsNullTerminator(c)) break; + + switch (state) + { + // I don't think this first case can actually be triggered, but Nintendo has it there anyway + case NormalizeState.Initial when pathWasSkipped && IsDot(c): state = NormalizeState.Dot; break; + case NormalizeState.Initial when IsSeparator(c): state = NormalizeState.FirstSeparator; break; + case NormalizeState.Initial when !pathWasSkipped: return ResultFs.InvalidPathFormat.Log(); + case NormalizeState.Initial: state = NormalizeState.Normal; break; + + case NormalizeState.Normal when IsSeparator(c): state = NormalizeState.Separator; break; + + case NormalizeState.FirstSeparator when IsDot(c): + case NormalizeState.Separator when IsDot(c): + state = NormalizeState.Dot; + break; + + case NormalizeState.FirstSeparator when IsSeparator(c): + case NormalizeState.Separator when IsSeparator(c): + isNormalized = false; + return Result.Success; + + case NormalizeState.FirstSeparator: + case NormalizeState.Separator: + state = NormalizeState.Normal; + break; + + case NormalizeState.Dot when IsSeparator(c): + isNormalized = false; + return Result.Success; + + case NormalizeState.Dot when IsDot(c): state = NormalizeState.DoubleDot; break; + case NormalizeState.Dot: state = NormalizeState.Normal; break; + + case NormalizeState.DoubleDot when IsSeparator(c): + isNormalized = false; + return Result.Success; + + case NormalizeState.DoubleDot: state = NormalizeState.Normal; break; + } + } + + switch (state) + { + case NormalizeState.Initial: + return ResultFs.InvalidPathFormat.Log(); + case NormalizeState.Normal: + isNormalized = true; + break; + case NormalizeState.FirstSeparator: + isNormalized = !isUncPath; + break; + case NormalizeState.Separator: + case NormalizeState.Dot: + case NormalizeState.DoubleDot: + isNormalized = false; + break; + } + + return Result.Success; + } + + public static Result ParseMountName(out U8Span pathAfterMount, Span outMountNameBuffer, + out long mountNameLength, U8Span path) + { + pathAfterMount = default; + mountNameLength = default; + + int mountStart = IsSeparator(path.GetOrNull(0)) ? 1 : 0; + int mountEnd; + + int maxMountLength = Math.Min(PathTools.MountNameLengthMax, path.Length - mountStart); + + for (mountEnd = mountStart; mountEnd <= maxMountLength; mountEnd++) + { + byte c = path[mountEnd]; + + if (IsSeparator(c)) + { + pathAfterMount = path; + mountNameLength = 0; + + return Result.Success; + } + + if (IsDriveSeparator(c)) + { + mountEnd++; + break; + } + + if (IsNullTerminator(c)) + { + break; + } + } + + if (mountStart >= mountEnd - 1 || !IsDriveSeparator(path[mountEnd - 1])) + return ResultFs.InvalidPathFormat.Log(); + + for (int i = mountStart; i < mountEnd; i++) + { + if (IsDot(path[i])) + return ResultFs.InvalidCharacter.Log(); + } + + if (!outMountNameBuffer.IsEmpty) + { + if (mountEnd - mountStart > outMountNameBuffer.Length) + return ResultFs.TooLongPath.Log(); + + path.Value.Slice(0, mountEnd).CopyTo(outMountNameBuffer); + } + + pathAfterMount = path.Slice(mountEnd); + mountNameLength = mountEnd - mountStart; + return Result.Success; + } + + private static Result ParseWindowsPath(out U8Span newPath, Span buffer, out long windowsPathLength, + out bool isNormalized, bool checkIfNormalized, U8Span path, bool hasMountName) + { + newPath = default; + windowsPathLength = 0; + isNormalized = checkIfNormalized; + + int winPathLen = 0; + int mountNameLen = 0; + bool skippedMount = false; + + bool needsSeparatorFixup = false; + + if (hasMountName) + { + if (IsSeparator(path.GetOrNull(0)) && IsAltSeparator(path.GetOrNull(1)) && + IsAltSeparator(path.GetOrNull(2))) + { + mountNameLen = 1; + skippedMount = true; + } + else + { + int separatorCount = 0; + + while (IsSeparator(path.GetOrNull(separatorCount))) + { + separatorCount++; + } + + if (separatorCount == 1 || PathUtility.IsWindowsDrive(path.Slice(separatorCount))) + { + mountNameLen = separatorCount; + skippedMount = true; + } + else if (separatorCount > 2 && checkIfNormalized) + { + isNormalized = false; + return Result.Success; + } + else if (separatorCount > 1) + { + mountNameLen = separatorCount - 2; + skippedMount = true; + } + } + } + else + { + if (IsSeparator(path.GetOrNull(0)) && !PathUtility.IsUnc(path)) + { + mountNameLen = 1; + skippedMount = true; + } + } + + U8Span pathTrimmed = path.Slice(mountNameLen); + + if (PathUtility.IsWindowsDrive(pathTrimmed)) + { + int i = 2; + + while (!IsAnySeparator(pathTrimmed.GetOrNull(i)) && !IsNullTerminator(pathTrimmed.GetOrNull(i))) + { + i++; + } + + winPathLen = mountNameLen + i; + + if (!buffer.IsEmpty) + { + if (winPathLen > buffer.Length) + { + return ResultFs.TooLongPath.Log(); + } + + path.Value.Slice(0, winPathLen).CopyTo(buffer); + } + + newPath = path.Slice(winPathLen); + windowsPathLength = winPathLen; + return Result.Success; + } + + // A UNC path should be in the format "\\" host-name "\" share-name [ "\" object-name ] + if (PathUtility.IsUnc(pathTrimmed)) + { + if (IsAnySeparator(pathTrimmed.GetOrNull(2))) + { + return ResultFs.InvalidPathFormat.Log(); + } + + int currentComponentStart = 2; + + for (int i = 2; ; i++) + { + byte c = pathTrimmed.GetOrNull(i); + + if (IsAnySeparator(c)) + { + // Nintendo feels the need to change the '\' separators to '/'s + if (IsAltSeparator(c)) + { + needsSeparatorFixup = true; + + if (checkIfNormalized) + { + isNormalized = false; + return Result.Success; + } + } + + // make sure share-name is not empty + if (currentComponentStart == 2 && IsSeparator(pathTrimmed.GetOrNull(i + 1))) + { + return ResultFs.InvalidPathFormat.Log(); + } + + int componentLength = i - currentComponentStart; + + // neither host-name nor share-name can be "." or ".." + if (componentLength == 1 && IsDot(pathTrimmed[currentComponentStart])) + { + return ResultFs.InvalidPathFormat.Log(); + } + + if (componentLength == 2 && IsDot(pathTrimmed[currentComponentStart]) && + IsDot(pathTrimmed[currentComponentStart + 1])) + { + return ResultFs.InvalidPathFormat.Log(); + } + + // If we're currently processing the share-name path component + if (currentComponentStart != 2) + { + winPathLen = mountNameLen + i; + break; + } + + currentComponentStart = i + 1; + } + else if (c == (byte)'$' || IsDriveSeparator(c)) + { + // '$' and ':' are not allowed in the host-name path component + if (currentComponentStart == 2) + { + return ResultFs.InvalidCharacter.Log(); + } + + // A '$' or ':' must be the last character in share-name + byte nextChar = pathTrimmed.GetOrNull(i + 1); + if (!IsSeparator(nextChar) && !IsNullTerminator(nextChar)) + { + return ResultFs.InvalidPathFormat.Log(); + } + + winPathLen = mountNameLen + i + 1; + break; + } + else if (IsNullTerminator(c)) + { + if (currentComponentStart != 2) + { + int componentLength = i - currentComponentStart; + + // neither host-name nor share-name can be "." or ".." + if (componentLength == 1 && IsDot(pathTrimmed[currentComponentStart])) + { + return ResultFs.InvalidPathFormat.Log(); + } + + if (componentLength == 2 && IsDot(pathTrimmed[currentComponentStart]) && + IsDot(pathTrimmed[currentComponentStart + 1])) + { + return ResultFs.InvalidPathFormat.Log(); + } + + winPathLen = mountNameLen + i; + } + + break; + } + } + + if (!buffer.IsEmpty) + { + if (winPathLen - mountNameLen > buffer.Length) + { + return ResultFs.TooLongPath.Log(); + } + + int outPos = 0; + + if (skippedMount) + { + buffer[0] = StringTraits.DirectorySeparator; + outPos++; + } + + pathTrimmed.Value.Slice(0, winPathLen - mountNameLen).CopyTo(buffer.Slice(outPos)); + + buffer[outPos] = StringTraits.AltDirectorySeparator; + buffer[outPos + 1] = StringTraits.AltDirectorySeparator; + + if (needsSeparatorFixup) + { + for (int i = mountNameLen + 2; i < winPathLen; i++) + { + if (IsAltSeparator(buffer[i])) + { + buffer[i] = StringTraits.DirectorySeparator; + } + } + } + + newPath = path.Slice(winPathLen); + windowsPathLength = outPos + winPathLen - mountNameLen; + return Result.Success; + } + } + newPath = path.Slice(winPathLen); + return Result.Success; + } + + private enum NormalizeState + { + Initial, + Normal, + FirstSeparator, + Separator, + Dot, + DoubleDot + } + } +} diff --git a/src/LibHac/Fs/PathUtility.cs b/src/LibHac/Fs/PathUtility.cs new file mode 100644 index 00000000..679126de --- /dev/null +++ b/src/LibHac/Fs/PathUtility.cs @@ -0,0 +1,32 @@ +using System.Runtime.CompilerServices; +using LibHac.Common; +using static LibHac.Fs.PathTool; + +namespace LibHac.Fs +{ + public static class PathUtility + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsWindowsDrive(U8Span path) + { + return (uint)path.Length > 1 && + (IsDriveSeparator(path[1]) && + IsWindowsDriveCharacter(path[0])); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsWindowsDriveCharacter(byte c) + { + return (0b1101_1111 & c) - 'A' <= 'Z' - 'A'; + //return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsUnc(U8Span path) + { + return (uint)path.Length > 1 && + (IsSeparator(path[0]) && IsSeparator(path[1]) || + IsAltSeparator(path[0]) && IsAltSeparator(path[1])); + } + } +} diff --git a/src/LibHac/Fs/StringTraits.cs b/src/LibHac/Fs/StringTraits.cs new file mode 100644 index 00000000..0cca0a33 --- /dev/null +++ b/src/LibHac/Fs/StringTraits.cs @@ -0,0 +1,11 @@ +namespace LibHac.Fs +{ + internal static class StringTraits + { + public const byte DirectorySeparator = (byte)'/'; + public const byte AltDirectorySeparator = (byte)'\\'; + public const byte DriveSeparator = (byte)':'; + public const byte Dot = (byte)'.'; + public const byte NullTerminator = 0; + } +} diff --git a/src/LibHac/FsService/PathNormalizer.cs b/src/LibHac/FsService/PathNormalizer.cs new file mode 100644 index 00000000..08f29259 --- /dev/null +++ b/src/LibHac/FsService/PathNormalizer.cs @@ -0,0 +1,45 @@ +using System; +using LibHac.Common; + +namespace LibHac.FsService +{ + public ref struct PathNormalizer + { + private U8Span _path; + private Result _result; + + public PathNormalizer(U8Span path, Option option) + { + if (option.HasFlag(Option.AcceptEmpty) && path.IsEmpty()) + { + _path = path; + _result = Result.Success; + } + else + { + bool preserveUnc = option.HasFlag(Option.PreserveUnc); + bool preserveTailSeparator = option.HasFlag(Option.PreserveTailSeparator); + bool hasMountName = option.HasFlag(Option.HasMountName); + _result = Normalize(out _path, path, preserveUnc, preserveTailSeparator, hasMountName); + } + } + + private static Result Normalize(out U8Span normalizedPath, U8Span path, bool preserveUnc, + bool preserveTailSeparator, bool hasMountName) + { + normalizedPath = default; + throw new NotImplementedException(); + + } + + [Flags] + public enum Option + { + None = 0, + PreserveUnc = (1 << 0), + PreserveTailSeparator = (1 << 1), + HasMountName = (1 << 2), + AcceptEmpty = (1 << 3), + } + } +} diff --git a/src/LibHac/FsSystem/PathTools.cs b/src/LibHac/FsSystem/PathTools.cs index 28628a78..55aac6da 100644 --- a/src/LibHac/FsSystem/PathTools.cs +++ b/src/LibHac/FsSystem/PathTools.cs @@ -9,9 +9,10 @@ namespace LibHac.FsSystem { public static class PathTools { + // todo: Consolidate these internal const char DirectorySeparator = '/'; internal const char MountSeparator = ':'; - internal const int MountNameLength = 0xF; + internal const int MountNameLengthMax = 0xF; // Todo: Remove internal const int MaxPathLength = 0x300; @@ -24,7 +25,7 @@ namespace LibHac.FsSystem var sb = new ValueStringBuilder(initialBuffer); int rootLen = 0; - int maxMountLen = Math.Min(inPath.Length, MountNameLength); + int maxMountLen = Math.Min(inPath.Length, MountNameLengthMax); for (int i = 0; i < maxMountLen; i++) { @@ -467,7 +468,7 @@ namespace LibHac.FsSystem public static Result GetMountNameLength(string path, out int length) { - int maxLen = Math.Min(path.Length, MountNameLength); + int maxLen = Math.Min(path.Length, MountNameLengthMax); for (int i = 0; i < maxLen; i++) { @@ -515,7 +516,7 @@ namespace LibHac.FsSystem int rootLength = 0; - for (int i = mountNameStart; i < mountNameStart + MountNameLength; i++) + for (int i = mountNameStart; i < mountNameStart + MountNameLengthMax; i++) { if (i >= path.Length || path[i] == 0) break; diff --git a/tests/LibHac.Tests/DebugAssertHandler.cs b/tests/LibHac.Tests/DebugAssertHandler.cs new file mode 100644 index 00000000..e7819464 --- /dev/null +++ b/tests/LibHac.Tests/DebugAssertHandler.cs @@ -0,0 +1,12 @@ +using System.Diagnostics; + +namespace LibHac.Tests +{ + public class DebugAssertHandler : DefaultTraceListener + { + public override void Fail(string message, string detailMessage) + { + // throw new System.NotImplementedException(message + detailMessage); + } + } +} diff --git a/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp b/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp new file mode 100644 index 00000000..fbace91e --- /dev/null +++ b/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp @@ -0,0 +1,279 @@ +// Uses GLoat to run code in nnsdk https://github.com/h1k421/GLoat +#include + +static char Buf[0x80000]; +static int BufPos = 0; + +static char ResultNameBuf[0x100]; + +namespace nn::fs::detail { + bool IsEnabledAccessLog(); +} + +namespace nn::fs::PathTool { + // SDK < 7 + nn::Result Normalize(char* buffer, uint64_t* outNormalizedPathLength, const char* path, uint64_t bufferLength, bool preserveUnc); + nn::Result IsNormalized(bool* outIsNormalized, const char* path); + + // SDK >= 7 + nn::Result Normalize(char* buffer, uint64_t* outNormalizedPathLength, const char* path, uint64_t bufferLength, bool preserveUnc, bool hasMountName); + nn::Result IsNormalized(bool* outIsNormalized, const char* path, bool preserveUnc, bool hasMountName); +} + +void* allocate(size_t size) +{ + void* ptr = malloc(size); + + char buffer[0x40]; + sprintf(buffer, "Allocating %ld. 0x%p", size, ptr); + svcOutputDebugString(buffer, sizeof(buffer)); + + return ptr; +} + +void deallocate(void* ptr, size_t size) +{ + char buffer[0x40]; + sprintf(buffer, "Deallocating %ld. 0x%p", size, ptr); + svcOutputDebugString(buffer, sizeof(buffer)); + + free(ptr); +} + +void setAllocators(void) { + nn::fs::SetAllocator(allocate, deallocate); +} + +const char* GetResultName(nn::Result result) { + switch (result.GetValue()) { + case 0: return "Result.Success"; + case 0x2EE402: return "ResultFs.InvalidPath.Value"; + case 0x2EE602: return "ResultFs.TooLongPath.Value"; + case 0x2EE802: return "ResultFs.InvalidCharacter.Value"; + case 0x2EEA02: return "ResultFs.InvalidPathFormat.Value"; + case 0x2EEC02: return "ResultFs.DirectoryUnobtainable.Value"; + default: + sprintf(ResultNameBuf, "0x%x", result.GetValue()); + return ResultNameBuf; + } +} + +void CreateNormalizeTestData(char const* path, bool preserveUnc, bool hasMountName) { + char normalized[0x200]; + uint64_t normalizedLen = 0; + memset(normalized, 0, 0x200); + + //svcOutputDebugString(path, strnlen(path, 0x200)); + + nn::Result result = nn::fs::PathTool::Normalize(normalized, &normalizedLen, path, 0x200, preserveUnc, hasMountName); + + const char* preserveUncStr = preserveUnc ? "true" : "false"; + const char* hasMountNameStr = hasMountName ? "true" : "false"; + BufPos += sprintf(&Buf[BufPos], "new object[] {@\"%s\", %s, %s, @\"%s\", %ld, %s},\n", + path, preserveUncStr, hasMountNameStr, normalized, normalizedLen, GetResultName(result)); +} + +void CreateIsNormalizedTestData(char const* path, bool preserveUnc, bool hasMountName) { + bool isNormalized = false; + + nn::Result result = nn::fs::PathTool::IsNormalized(&isNormalized, path, preserveUnc, hasMountName); + + const char* preserveUncStr = preserveUnc ? "true" : "false"; + const char* hasMountNameStr = hasMountName ? "true" : "false"; + const char* isNormalizedStr = isNormalized ? "true" : "false"; + BufPos += sprintf(&Buf[BufPos], "new object[] {@\"%s\", %s, %s, %s, %s},\n", + path, preserveUncStr, hasMountNameStr, isNormalizedStr, GetResultName(result)); +} + +void CreateTestDataWithParentDirs(char const* path, bool preserveUnc, bool hasMountName, void (*func)(char const*, bool, bool), int parentCount) { + char parentPath[0x200]; + memset(parentPath, 0, sizeof(parentPath)); + + strcpy(parentPath, path); + func(parentPath, preserveUnc, hasMountName); + + for (int i = 0; i < parentCount; i++) { + strcat(parentPath, "/.."); + func(parentPath, preserveUnc, hasMountName); + } +} + +void CreateTestDataWithParentDirs(char const* path, bool preserveUnc, bool hasMountName, void (*func)(char const*, bool, bool)) { + CreateTestDataWithParentDirs(path, preserveUnc, hasMountName, func, 3); +} + +void CreateTestData(void (*func)(char const*, bool, bool)) { + Buf[0] = '\n'; + BufPos = 1; + + bool preserveUnc = false; + + func("", preserveUnc, false); + func("/", preserveUnc, false); + func("/.", preserveUnc, false); + func("/a/b/c", preserveUnc, false); + func("/a/b/../c", preserveUnc, false); + func("/a/b/c/..", preserveUnc, false); + func("/a/b/c/.", preserveUnc, false); + func("/a/../../..", preserveUnc, false); + func("/a/../../../a/b/c", preserveUnc, false); + func("//a/b//.//c", preserveUnc, false); + func("/../a/b/c/.", preserveUnc, false); + func("/./aaa/bbb/ccc/.", preserveUnc, false); + func("/a/b/c/", preserveUnc, false); + func("a/b/c/", preserveUnc, false); + func("/aa/./bb/../cc/", preserveUnc, false); + func("/./b/../c/", preserveUnc, false); + func("/a/../../../", preserveUnc, false); + func("//a/b//.//c/", preserveUnc, false); + func("/tmp/../", preserveUnc, false); + func("a", preserveUnc, false); + func("a/../../../a/b/c", preserveUnc, false); + func("./b/../c/", preserveUnc, false); + func(".", preserveUnc, false); + func("..", preserveUnc, false); + func("../a/b/c/.", preserveUnc, false); + func("./a/b/c/.", preserveUnc, false); + func("abc", preserveUnc, false); + func("mount:/a/b/../c", preserveUnc, true); + func("a:/a/b/c", preserveUnc, true); + func("mount:/a/b/../c", preserveUnc, true); + func("mount:\\a/b/../c", preserveUnc, true); + func("mount:\\a/b\\../c", preserveUnc, true); + func("mount:\\a/b/c", preserveUnc, true); + func("mount:/a\\../b\\..c", preserveUnc, true); + func("mount:/a\\../b/..cd", preserveUnc, true); + func("mount:/a\\..d/b/c\\..", preserveUnc, true); + func("mount:", preserveUnc, true); + func("abc:/a/../../../a/b/c", preserveUnc, true); + func("abc:/./b/../c/", preserveUnc, true); + func("abc:/.", preserveUnc, true); + func("abc:/..", preserveUnc, true); + func("abc:/", preserveUnc, true); + func("abc://a/b//.//c", preserveUnc, true); + func("abc:/././/././a/b//.//c", preserveUnc, true); + func("mount:/d./aa", preserveUnc, true); + func("mount:/d/..", preserveUnc, true); + func("/path/aaa/bbb\\..\\h/ddd", preserveUnc, false); + func("/path/aaa/bbb/../h/ddd", preserveUnc, false); + func("/path/aaa/bbb\\.\\h/ddd", preserveUnc, false); + func("/path/aaa/bbb\\./h/ddd", preserveUnc, false); + func("/path/aaa/bbb/./h/ddd", preserveUnc, false); + func("mount:abcd", preserveUnc, true); + func("mount:", preserveUnc, true); + func("mount:/", preserveUnc, true); + func("mount:\\..", preserveUnc, true); + func("mount:/a/b\\..", preserveUnc, true); + func("mount:/dir", preserveUnc, true); + func("mount:/dir/", preserveUnc, true); + func("mount:\\", preserveUnc, true); + func("mo.unt:\\", preserveUnc, true); + func("mount.:\\", preserveUnc, true); + func("mount:./aa/bb", preserveUnc, true); + //func("mount:../aa/bb", preserveUnc, true); // crashes nnsdk + func("mount:.../aa/bb", preserveUnc, true); + func("mount:...aa/bb", preserveUnc, true); + func("...aa/bb", preserveUnc, false); + func("mount01234567890/aa/bb", preserveUnc, true); + func("mount01234567890:/aa/bb", preserveUnc, true); + func("mount0123456789:/aa/bb", preserveUnc, true); + func("mount012345678:/aa/bb", preserveUnc, true); + func("mount:aa/..\\bb", preserveUnc, true); + func("mount:..\\bb", preserveUnc, true); + func("mount:/..\\bb", preserveUnc, true); + func("mount:/.\\bb", preserveUnc, true); + func("mount:\\..\\bb", preserveUnc, true); + func("mount:\\.\\bb", preserveUnc, true); + func("mount:/a\\..\\bb", preserveUnc, true); + func("mount:/a\\.\\bb", preserveUnc, true); + + for (int i = 0; i < 2; i++) { + preserveUnc = (bool)i; + + CreateTestDataWithParentDirs("//$abc/bb", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//:abc/bb", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("\\\\\\asd", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("\\\\/asd", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("\\\\//asd", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("\\\\a/b/cc/../d", preserveUnc, false, func); + CreateTestDataWithParentDirs("c:/aa/bb", preserveUnc, true, func); + CreateTestDataWithParentDirs("mount:\\c:/aa", preserveUnc, true, func); + CreateTestDataWithParentDirs("mount:/c:\\aa/bb", preserveUnc, true, func); + CreateTestDataWithParentDirs("mount:////c:\\aa/bb", preserveUnc, true, func); + CreateTestDataWithParentDirs("mount:/\\aa/bb", preserveUnc, true, func); + CreateTestDataWithParentDirs("mount:/c:/aa/bb", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("mount:c:/aa/bb", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("mount:c:/aa/bb", preserveUnc, true, func, 0); + CreateTestDataWithParentDirs("mount:/\\aa/../b", preserveUnc, true, func, 2); + CreateTestDataWithParentDirs("mount://aa/bb", preserveUnc, true, func, 1); + CreateTestDataWithParentDirs("//aa/bb", preserveUnc, true, func, 1); + CreateTestDataWithParentDirs("//aa/bb", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("/aa/bb", preserveUnc, false, func); + CreateTestDataWithParentDirs("c:/aa", preserveUnc, false, func, 2); + CreateTestDataWithParentDirs("c:abcde/aa/bb", preserveUnc, false, func); + CreateTestDataWithParentDirs("c:abcde", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("c:abcde/", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("///aa", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//aa//bb", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("//./bb", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//../bb", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//.../bb", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//aa$abc/bb", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//aa$/bb", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//aa:/bb", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//aa/bb$b/cc$", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//aa/bb/cc$c", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("//aa/bb/cc$c/dd", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//aa/bb", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//aa/bb/cc//dd", preserveUnc, false, func); + CreateTestDataWithParentDirs("//aa/bb/cc\\/dd", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//aa/bb/cc//dd", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//aa/bb/cc/dd", preserveUnc, false, func); + CreateTestDataWithParentDirs("//aa/bb/cc/\\dd", preserveUnc, false, func); + CreateTestDataWithParentDirs("//aa/../", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//aa//", preserveUnc, false, func, 0); + CreateTestDataWithParentDirs("//aa/bb..", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("//aa/bb../", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("/\\\\aa/bb/cc/..", preserveUnc, true, func); + + CreateTestDataWithParentDirs("c:aa\\bb/cc", preserveUnc, false, func); + CreateTestDataWithParentDirs("c:\\//\\aa\\bb", preserveUnc, false, func, 1); + + CreateTestDataWithParentDirs("mount://////a/bb/c", preserveUnc, true, func, 2); + + CreateTestDataWithParentDirs("//", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("//a", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("//a", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("//a/", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("//a/b", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("//a/b/", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("//a/b/c", preserveUnc, false, func, 2); + CreateTestDataWithParentDirs("//a/b/c/", preserveUnc, false, func, 2); + + CreateTestDataWithParentDirs("\\\\", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("\\\\a", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("\\\\a/", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("\\\\a/b", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("\\\\a/b/", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("\\\\a/b/c", preserveUnc, false, func, 2); + CreateTestDataWithParentDirs("\\\\a/b/c/", preserveUnc, false, func, 2); + + CreateTestDataWithParentDirs("\\\\", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("\\\\a", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("\\\\a\\", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("\\\\a\\b", preserveUnc, false, func, 1); + CreateTestDataWithParentDirs("\\\\a\\b\\", preserveUnc, false, func, 1); // "\\a\b\/../.." crashes nnsdk + CreateTestDataWithParentDirs("\\\\a\\b\\c", preserveUnc, false, func, 2); + CreateTestDataWithParentDirs("\\\\a\\b\\c\\", preserveUnc, false, func, 2); + } + + svcOutputDebugString(Buf, BufPos); +} + +extern "C" void nnMain(void) { + //setAllocators(); + nn::fs::detail::IsEnabledAccessLog(); // Adds the sdk version to the output when not calling setAllocators + CreateTestData(CreateNormalizeTestData); + CreateTestData(CreateIsNormalizedTestData); +} \ No newline at end of file diff --git a/tests/LibHac.Tests/Fs/PathToolTests.cs b/tests/LibHac.Tests/Fs/PathToolTests.cs new file mode 100644 index 00000000..1e12a840 --- /dev/null +++ b/tests/LibHac.Tests/Fs/PathToolTests.cs @@ -0,0 +1,817 @@ +using LibHac.Common; +using LibHac.Fs; +using Xunit; + +namespace LibHac.Tests.Fs +{ + public class PathToolTests + { + [Theory] + [MemberData(nameof(NormalizeTestItems))] + public static void Normalize(string path, bool preserveUnc, bool hasMountName, string expectedNormalized, + long expectedLength, Result expectedResult) + { + var buffer = new byte[0x301]; + + Result result = PathTool.Normalize(buffer, out long normalizedLength, path.ToU8Span(), preserveUnc, + hasMountName); + + Assert.Equal(expectedResult, result); + Assert.Equal(expectedNormalized, StringUtils.Utf8ZToString(buffer)); + Assert.Equal(expectedLength, normalizedLength); + } + + [Theory] + [MemberData(nameof(IsNormalizedTestItems))] + public static void IsNormalized(string path, bool preserveUnc, bool hasMountName, bool expectedIsNormalized, + Result expectedResult) + { + Result result = PathTool.IsNormalized(out bool isNormalized, path.ToU8Span(), preserveUnc, hasMountName); + + Assert.Equal(expectedResult, result); + + if (result.IsSuccess()) + { + Assert.Equal(expectedIsNormalized, isNormalized); + } + } + + public static object[][] NormalizeTestItems = + { + new object[] {@"", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"/", false, false, @"/", 1, Result.Success}, + new object[] {@"/.", false, false, @"/", 1, Result.Success}, + new object[] {@"/a/b/c", false, false, @"/a/b/c", 6, Result.Success}, + new object[] {@"/a/b/../c", false, false, @"/a/c", 4, Result.Success}, + new object[] {@"/a/b/c/..", false, false, @"/a/b", 4, Result.Success}, + new object[] {@"/a/b/c/.", false, false, @"/a/b/c", 6, Result.Success}, + new object[] {@"/a/../../..", false, false, @"/a/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"/a/../../../a/b/c", false, false, @"/a/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"//a/b//.//c", false, false, @"/a/b/c", 6, Result.Success}, + new object[] {@"/../a/b/c/.", false, false, @"/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"/./aaa/bbb/ccc/.", false, false, @"/aaa/bbb/ccc", 12, Result.Success}, + new object[] {@"/a/b/c/", false, false, @"/a/b/c", 6, Result.Success}, + new object[] {@"a/b/c/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"/aa/./bb/../cc/", false, false, @"/aa/cc", 6, Result.Success}, + new object[] {@"/./b/../c/", false, false, @"/c", 2, Result.Success}, + new object[] {@"/a/../../../", false, false, @"/a/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"//a/b//.//c/", false, false, @"/a/b/c", 6, Result.Success}, + new object[] {@"/tmp/../", false, false, @"/", 1, Result.Success}, + new object[] {@"a", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"a/../../../a/b/c", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"./b/../c/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@".", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"../a/b/c/.", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"./a/b/c/.", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"abc", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:/a/b/../c", false, true, @"mount:/a/c", 10, Result.Success}, + new object[] {@"a:/a/b/c", false, true, @"a:/a/b/c", 8, Result.Success}, + new object[] {@"mount:/a/b/../c", false, true, @"mount:/a/c", 10, Result.Success}, + new object[] {@"mount:\a/b/../c", false, true, @"mount:\a/c", 10, Result.Success}, + new object[] {@"mount:\a/b\../c", false, true, @"mount:/a/c", 10, Result.Success}, + new object[] {@"mount:\a/b/c", false, true, @"mount:\a/b/c", 12, Result.Success}, + new object[] {@"mount:/a\../b\..c", false, true, @"mount:/b\..c", 12, Result.Success}, + new object[] {@"mount:/a\../b/..cd", false, true, @"mount:/b/..cd", 13, Result.Success}, + new object[] {@"mount:/a\..d/b/c\..", false, true, @"mount:/a\..d/b", 14, Result.Success}, + new object[] {@"mount:", false, true, @"mount:/", 7, Result.Success}, + new object[] {@"abc:/a/../../../a/b/c", false, true, @"abc:/a/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"abc:/./b/../c/", false, true, @"abc:/c", 6, Result.Success}, + new object[] {@"abc:/.", false, true, @"abc:/", 5, Result.Success}, + new object[] {@"abc:/..", false, true, @"abc:/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"abc:/", false, true, @"abc:/", 5, Result.Success}, + new object[] {@"abc://a/b//.//c", false, true, @"abc:/a/b/c", 10, Result.Success}, + new object[] {@"abc:/././/././a/b//.//c", false, true, @"abc:/a/b/c", 10, Result.Success}, + new object[] {@"mount:/d./aa", false, true, @"mount:/d./aa", 12, Result.Success}, + new object[] {@"mount:/d/..", false, true, @"mount:/", 7, Result.Success}, + new object[] {@"/path/aaa/bbb\..\h/ddd", false, false, @"/path/aaa/h/ddd", 15, Result.Success}, + new object[] {@"/path/aaa/bbb/../h/ddd", false, false, @"/path/aaa/h/ddd", 15, Result.Success}, + new object[] {@"/path/aaa/bbb\.\h/ddd", false, false, @"/path/aaa/bbb\.\h/ddd", 21, Result.Success}, + new object[] {@"/path/aaa/bbb\./h/ddd", false, false, @"/path/aaa/bbb\./h/ddd", 21, Result.Success}, + new object[] {@"/path/aaa/bbb/./h/ddd", false, false, @"/path/aaa/bbb/h/ddd", 19, Result.Success}, + new object[] {@"mount:abcd", false, true, @"mount:abcd", 10, Result.Success}, + new object[] {@"mount:", false, true, @"mount:/", 7, Result.Success}, + new object[] {@"mount:/", false, true, @"mount:/", 7, Result.Success}, + new object[] {@"mount:\..", false, true, @"mount:/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount:/a/b\..", false, true, @"mount:/a", 8, Result.Success}, + new object[] {@"mount:/dir", false, true, @"mount:/dir", 10, Result.Success}, + new object[] {@"mount:/dir/", false, true, @"mount:/dir", 10, Result.Success}, + new object[] {@"mount:\", false, true, @"mount:\", 7, Result.Success}, + new object[] {@"mo.unt:\", false, true, @"", 0, ResultFs.InvalidCharacter.Value}, + new object[] {@"mount.:\", false, true, @"", 0, ResultFs.InvalidCharacter.Value}, + new object[] {@"mount:./aa/bb", false, true, @"mount:aa/bb", 11, Result.Success}, + new object[] {@"mount:.../aa/bb", false, true, @"mount:.../aa/bb", 15, Result.Success}, + new object[] {@"mount:...aa/bb", false, true, @"mount:...aa/bb", 14, Result.Success}, + new object[] {@"...aa/bb", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount01234567890/aa/bb", false, true, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount01234567890:/aa/bb", false, true, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount0123456789:/aa/bb", false, true, @"mount0123456789:/aa/bb", 22, Result.Success}, + new object[] {@"mount012345678:/aa/bb", false, true, @"mount012345678:/aa/bb", 21, Result.Success}, + new object[] {@"mount:aa/..\bb", false, true, @"mount:aa/..\bb", 14, Result.Success}, + new object[] {@"mount:..\bb", false, true, @"mount:..\bb", 11, Result.Success}, + new object[] {@"mount:/..\bb", false, true, @"mount:/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount:/.\bb", false, true, @"mount:/.\bb", 11, Result.Success}, + new object[] {@"mount:\..\bb", false, true, @"mount:/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount:\.\bb", false, true, @"mount:\.\bb", 11, Result.Success}, + new object[] {@"mount:/a\..\bb", false, true, @"mount:/bb", 9, Result.Success}, + new object[] {@"mount:/a\.\bb", false, true, @"mount:/a\.\bb", 13, Result.Success}, + new object[] {@"//$abc/bb", false, false, @"/$abc/bb", 8, Result.Success}, + new object[] {@"//:abc/bb", false, false, @"/:abc/bb", 8, Result.Success}, + new object[] {@"\\\asd", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\/asd", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\//asd", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//", false, false, @"/", 1, Result.Success}, + new object[] {@"///..", false, false, @"/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"\\a/b/cc/../d", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/cc/../d/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/cc/../d/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/cc/../d/../../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:/aa/bb", false, true, @"c:/aa/bb", 8, Result.Success}, + new object[] {@"c:/aa/bb/..", false, true, @"c:/aa", 5, Result.Success}, + new object[] {@"c:/aa/bb/../..", false, true, @"c:/", 3, Result.Success}, + new object[] {@"c:/aa/bb/../../..", false, true, @"c:/aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount:\c:/aa", false, true, @"mount:\c:/aa", 12, Result.Success}, + new object[] {@"mount:\c:/aa/..", false, true, @"mount:\c:", 9, Result.Success}, + new object[] {@"mount:\c:/aa/../..", false, true, @"mount:/", 7, Result.Success}, + new object[] {@"mount:\c:/aa/../../..", false, true, @"mount:/c:/aa/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount:/c:\aa/bb", false, true, @"mount:/c:\aa/bb", 15, Result.Success}, + new object[] {@"mount:/c:\aa/bb/..", false, true, @"mount:/c:\aa", 12, Result.Success}, + new object[] {@"mount:/c:\aa/bb/../..", false, true, @"mount:/", 7, Result.Success}, + new object[] {@"mount:/c:\aa/bb/../../..", false, true, @"mount:/c:\aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount:////c:\aa/bb", false, true, @"mount:/c:\aa/bb", 15, Result.Success}, + new object[] {@"mount:////c:\aa/bb/..", false, true, @"mount:/c:\aa", 12, Result.Success}, + new object[] {@"mount:////c:\aa/bb/../..", false, true, @"mount:/", 7, Result.Success}, + new object[] {@"mount:////c:\aa/bb/../../..", false, true, @"mount:/c:\aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount:/\aa/bb", false, true, @"mount:/\aa/bb", 13, Result.Success}, + new object[] {@"mount:/\aa/bb/..", false, true, @"mount:/\aa", 10, Result.Success}, + new object[] {@"mount:/\aa/bb/../..", false, true, @"mount:/", 7, Result.Success}, + new object[] {@"mount:/\aa/bb/../../..", false, true, @"mount:/\aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount:/c:/aa/bb", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:c:/aa/bb", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:c:/aa/bb", false, true, @"mount:c:/aa/bb", 14, Result.Success}, + new object[] {@"mount:/\aa/../b", false, true, @"mount:/b", 8, Result.Success}, + new object[] {@"mount:/\aa/../b/..", false, true, @"mount:/", 7, Result.Success}, + new object[] {@"mount:/\aa/../b/../..", false, true, @"mount:/b/a/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount://aa/bb", false, true, @"mount:/aa/bb", 12, Result.Success}, + new object[] {@"mount://aa/bb/..", false, true, @"mount:/aa", 9, Result.Success}, + new object[] {@"//aa/bb", false, true, @"/aa/bb", 6, Result.Success}, + new object[] {@"//aa/bb/..", false, true, @"/aa", 3, Result.Success}, + new object[] {@"//aa/bb", false, false, @"/aa/bb", 6, Result.Success}, + new object[] {@"//aa/bb/..", false, false, @"/aa", 3, Result.Success}, + new object[] {@"/aa/bb", false, false, @"/aa/bb", 6, Result.Success}, + new object[] {@"/aa/bb/..", false, false, @"/aa", 3, Result.Success}, + new object[] {@"/aa/bb/../..", false, false, @"/", 1, Result.Success}, + new object[] {@"/aa/bb/../../..", false, false, @"/aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"c:/aa", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:/aa/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:/aa/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde/aa/bb", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde/aa/bb/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde/aa/bb/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde/aa/bb/../../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"///aa", false, false, @"/aa", 3, Result.Success}, + new object[] {@"//aa//bb", false, false, @"/aa/bb", 6, Result.Success}, + new object[] {@"//aa//bb/..", false, false, @"/aa", 3, Result.Success}, + new object[] {@"//./bb", false, false, @"/bb", 3, Result.Success}, + new object[] {@"//../bb", false, false, @"/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"//.../bb", false, false, @"/.../bb", 7, Result.Success}, + new object[] {@"//aa$abc/bb", false, false, @"/aa$abc/bb", 10, Result.Success}, + new object[] {@"//aa$/bb", false, false, @"/aa$/bb", 7, Result.Success}, + new object[] {@"//aa:/bb", false, false, @"/aa:/bb", 7, Result.Success}, + new object[] {@"//aa/bb$b/cc$", false, false, @"/aa/bb$b/cc$", 12, Result.Success}, + new object[] {@"//aa/bb/cc$c", false, false, @"/aa/bb/cc$c", 11, Result.Success}, + new object[] {@"//aa/bb/cc$c/..", false, false, @"/aa/bb", 6, Result.Success}, + new object[] {@"//aa/bb/cc$c/dd", false, false, @"/aa/bb/cc$c/dd", 14, Result.Success}, + new object[] {@"//aa/bb", false, false, @"/aa/bb", 6, Result.Success}, + new object[] {@"//aa/bb/cc//dd", false, false, @"/aa/bb/cc/dd", 12, Result.Success}, + new object[] {@"//aa/bb/cc//dd/..", false, false, @"/aa/bb/cc", 9, Result.Success}, + new object[] {@"//aa/bb/cc//dd/../..", false, false, @"/aa/bb", 6, Result.Success}, + new object[] {@"//aa/bb/cc//dd/../../..", false, false, @"/aa", 3, Result.Success}, + new object[] {@"//aa/bb/cc\/dd", false, false, @"/aa/bb/cc\/dd", 13, Result.Success}, + new object[] {@"//aa/bb/cc//dd", false, false, @"/aa/bb/cc/dd", 12, Result.Success}, + new object[] {@"//aa/bb/cc/dd", false, false, @"/aa/bb/cc/dd", 12, Result.Success}, + new object[] {@"//aa/bb/cc/dd/..", false, false, @"/aa/bb/cc", 9, Result.Success}, + new object[] {@"//aa/bb/cc/dd/../..", false, false, @"/aa/bb", 6, Result.Success}, + new object[] {@"//aa/bb/cc/dd/../../..", false, false, @"/aa", 3, Result.Success}, + new object[] {@"//aa/bb/cc/\dd", false, false, @"/aa/bb/cc/\dd", 13, Result.Success}, + new object[] {@"//aa/bb/cc/\dd/..", false, false, @"/aa/bb/cc", 9, Result.Success}, + new object[] {@"//aa/bb/cc/\dd/../..", false, false, @"/aa/bb", 6, Result.Success}, + new object[] {@"//aa/bb/cc/\dd/../../..", false, false, @"/aa", 3, Result.Success}, + new object[] {@"//aa/../", false, false, @"/", 1, Result.Success}, + new object[] {@"//aa//", false, false, @"/aa", 3, Result.Success}, + new object[] {@"//aa/bb..", false, false, @"/aa/bb..", 8, Result.Success}, + new object[] {@"//aa/bb../..", false, false, @"/aa", 3, Result.Success}, + new object[] {@"//aa/bb../", false, false, @"/aa/bb..", 8, Result.Success}, + new object[] {@"//aa/bb..//..", false, false, @"/aa", 3, Result.Success}, + new object[] {@"/\\aa/bb/cc/..", false, true, @"/\\aa/bb", 8, Result.Success}, + new object[] {@"/\\aa/bb/cc/../..", false, true, @"/\\aa", 5, Result.Success}, + new object[] {@"/\\aa/bb/cc/../../..", false, true, @"/", 1, Result.Success}, + new object[] {@"/\\aa/bb/cc/../../../..", false, true, @"/\\aa/bb/cc/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"c:aa\bb/cc", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:aa\bb/cc/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:aa\bb/cc/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:aa\bb/cc/../../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:\//\aa\bb", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:\//\aa\bb/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount://////a/bb/c", false, true, @"mount:/a/bb/c", 13, Result.Success}, + new object[] {@"mount://////a/bb/c/..", false, true, @"mount:/a/bb", 11, Result.Success}, + new object[] {@"mount://////a/bb/c/../..", false, true, @"mount:/a", 8, Result.Success}, + new object[] {@"//", false, false, @"/", 1, Result.Success}, + new object[] {@"///..", false, false, @"/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"//a", false, false, @"/a", 2, Result.Success}, + new object[] {@"//a/..", false, false, @"/", 1, Result.Success}, + new object[] {@"//a", false, false, @"/a", 2, Result.Success}, + new object[] {@"//a/..", false, false, @"/", 1, Result.Success}, + new object[] {@"//a/", false, false, @"/a", 2, Result.Success}, + new object[] {@"//a//..", false, false, @"/", 1, Result.Success}, + new object[] {@"//a/b", false, false, @"/a/b", 4, Result.Success}, + new object[] {@"//a/b/..", false, false, @"/a", 2, Result.Success}, + new object[] {@"//a/b/", false, false, @"/a/b", 4, Result.Success}, + new object[] {@"//a/b//..", false, false, @"/a", 2, Result.Success}, + new object[] {@"//a/b/c", false, false, @"/a/b/c", 6, Result.Success}, + new object[] {@"//a/b/c/..", false, false, @"/a/b", 4, Result.Success}, + new object[] {@"//a/b/c/../..", false, false, @"/a", 2, Result.Success}, + new object[] {@"//a/b/c/", false, false, @"/a/b/c", 6, Result.Success}, + new object[] {@"//a/b/c//..", false, false, @"/a/b", 4, Result.Success}, + new object[] {@"//a/b/c//../..", false, false, @"/a", 2, Result.Success}, + new object[] {@"\\", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a//..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b//..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/c", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/c/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/c/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/c/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/c//..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/c//../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\c", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\c/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\c/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\c\", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\c\/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\c\/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//$abc/bb", true, false, @"", 0, ResultFs.InvalidCharacter.Value}, + new object[] {@"//:abc/bb", true, false, @"", 0, ResultFs.InvalidCharacter.Value}, + new object[] {@"\\\asd", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\/asd", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\//asd", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//", true, false, @"/", 1, Result.Success}, + new object[] {@"///..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/cc/../d", true, false, @"\\a/b/d", 7, Result.Success}, + new object[] {@"\\a/b/cc/../d/..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a/b/cc/../d/../..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a/b/cc/../d/../../..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"c:/aa/bb", true, true, @"c:/aa/bb", 8, Result.Success}, + new object[] {@"c:/aa/bb/..", true, true, @"c:/aa", 5, Result.Success}, + new object[] {@"c:/aa/bb/../..", true, true, @"c:/", 3, Result.Success}, + new object[] {@"c:/aa/bb/../../..", true, true, @"c:/aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount:\c:/aa", true, true, @"mount:\c:/aa", 12, Result.Success}, + new object[] {@"mount:\c:/aa/..", true, true, @"mount:\c:", 9, Result.Success}, + new object[] {@"mount:\c:/aa/../..", true, true, @"mount:/", 7, Result.Success}, + new object[] {@"mount:\c:/aa/../../..", true, true, @"mount:/c:/aa/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount:/c:\aa/bb", true, true, @"mount:/c:\aa/bb", 15, Result.Success}, + new object[] {@"mount:/c:\aa/bb/..", true, true, @"mount:/c:\aa", 12, Result.Success}, + new object[] {@"mount:/c:\aa/bb/../..", true, true, @"mount:/c:", 9, Result.Success}, + new object[] {@"mount:/c:\aa/bb/../../..", true, true, @"mount:/c:", 9, Result.Success}, + new object[] {@"mount:////c:\aa/bb", true, true, @"mount:////c:\aa/bb", 18, Result.Success}, + new object[] {@"mount:////c:\aa/bb/..", true, true, @"mount:////c:\aa", 15, Result.Success}, + new object[] {@"mount:////c:\aa/bb/../..", true, true, @"mount:////c:", 12, Result.Success}, + new object[] {@"mount:////c:\aa/bb/../../..", true, true, @"mount:////c:", 12, Result.Success}, + new object[] {@"mount:/\aa/bb", true, true, @"mount:/\aa/bb", 13, Result.Success}, + new object[] {@"mount:/\aa/bb/..", true, true, @"mount:/\aa", 10, Result.Success}, + new object[] {@"mount:/\aa/bb/../..", true, true, @"mount:/", 7, Result.Success}, + new object[] {@"mount:/\aa/bb/../../..", true, true, @"mount:/\aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount:/c:/aa/bb", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:c:/aa/bb", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:c:/aa/bb", true, true, @"mount:c:/aa/bb", 14, Result.Success}, + new object[] {@"mount:/\aa/../b", true, true, @"mount:/b", 8, Result.Success}, + new object[] {@"mount:/\aa/../b/..", true, true, @"mount:/", 7, Result.Success}, + new object[] {@"mount:/\aa/../b/../..", true, true, @"mount:/b/a/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"mount://aa/bb", true, true, @"mount:/\\aa/bb", 14, Result.Success}, + new object[] {@"mount://aa/bb/..", true, true, @"mount:/\\aa/bb", 14, Result.Success}, + new object[] {@"//aa/bb", true, true, @"/\\aa/bb", 8, Result.Success}, + new object[] {@"//aa/bb/..", true, true, @"/\\aa/bb", 8, Result.Success}, + new object[] {@"//aa/bb", true, false, @"\\aa/bb", 7, Result.Success}, + new object[] {@"//aa/bb/..", true, false, @"\\aa/bb", 7, Result.Success}, + new object[] {@"/aa/bb", true, false, @"/aa/bb", 6, Result.Success}, + new object[] {@"/aa/bb/..", true, false, @"/aa", 3, Result.Success}, + new object[] {@"/aa/bb/../..", true, false, @"/", 1, Result.Success}, + new object[] {@"/aa/bb/../../..", true, false, @"/aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, + new object[] {@"c:/aa", true, false, @"c:/aa", 5, Result.Success}, + new object[] {@"c:/aa/..", true, false, @"c:", 2, Result.Success}, + new object[] {@"c:/aa/../..", true, false, @"c:", 2, Result.Success}, + new object[] {@"c:abcde/aa/bb", true, false, @"c:abcde/aa/bb", 13, Result.Success}, + new object[] {@"c:abcde/aa/bb/..", true, false, @"c:abcde/aa", 10, Result.Success}, + new object[] {@"c:abcde/aa/bb/../..", true, false, @"c:abcde", 7, Result.Success}, + new object[] {@"c:abcde/aa/bb/../../..", true, false, @"c:abcde", 7, Result.Success}, + new object[] {@"c:abcde", true, false, @"c:abcde", 7, Result.Success}, + new object[] {@"c:abcde/..", true, false, @"c:abcde", 7, Result.Success}, + new object[] {@"c:abcde/", true, false, @"c:abcde", 7, Result.Success}, + new object[] {@"///aa", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//aa//bb", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//aa//bb/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//./bb", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//../bb", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//.../bb", true, false, @"\\.../bb", 8, Result.Success}, + new object[] {@"//aa$abc/bb", true, false, @"", 0, ResultFs.InvalidCharacter.Value}, + new object[] {@"//aa$/bb", true, false, @"", 0, ResultFs.InvalidCharacter.Value}, + new object[] {@"//aa:/bb", true, false, @"", 0, ResultFs.InvalidCharacter.Value}, + new object[] {@"//aa/bb$b/cc$", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//aa/bb/cc$c", true, false, @"\\aa/bb/cc$c", 12, Result.Success}, + new object[] {@"//aa/bb/cc$c/..", true, false, @"\\aa/bb", 7, Result.Success}, + new object[] {@"//aa/bb/cc$c/dd", true, false, @"\\aa/bb/cc$c/dd", 15, Result.Success}, + new object[] {@"//aa/bb", true, false, @"\\aa/bb", 7, Result.Success}, + new object[] {@"//aa/bb/cc//dd", true, false, @"\\aa/bb/cc/dd", 13, Result.Success}, + new object[] {@"//aa/bb/cc//dd/..", true, false, @"\\aa/bb/cc", 10, Result.Success}, + new object[] {@"//aa/bb/cc//dd/../..", true, false, @"\\aa/bb", 7, Result.Success}, + new object[] {@"//aa/bb/cc//dd/../../..", true, false, @"\\aa/bb", 7, Result.Success}, + new object[] {@"//aa/bb/cc\/dd", true, false, @"\\aa/bb/cc\/dd", 14, Result.Success}, + new object[] {@"//aa/bb/cc//dd", true, false, @"\\aa/bb/cc/dd", 13, Result.Success}, + new object[] {@"//aa/bb/cc/dd", true, false, @"\\aa/bb/cc/dd", 13, Result.Success}, + new object[] {@"//aa/bb/cc/dd/..", true, false, @"\\aa/bb/cc", 10, Result.Success}, + new object[] {@"//aa/bb/cc/dd/../..", true, false, @"\\aa/bb", 7, Result.Success}, + new object[] {@"//aa/bb/cc/dd/../../..", true, false, @"\\aa/bb", 7, Result.Success}, + new object[] {@"//aa/bb/cc/\dd", true, false, @"\\aa/bb/cc/\dd", 14, Result.Success}, + new object[] {@"//aa/bb/cc/\dd/..", true, false, @"\\aa/bb/cc", 10, Result.Success}, + new object[] {@"//aa/bb/cc/\dd/../..", true, false, @"\\aa/bb", 7, Result.Success}, + new object[] {@"//aa/bb/cc/\dd/../../..", true, false, @"\\aa/bb", 7, Result.Success}, + new object[] {@"//aa/../", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//aa//", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//aa/bb..", true, false, @"\\aa/bb..", 9, Result.Success}, + new object[] {@"//aa/bb../..", true, false, @"\\aa/bb..", 9, Result.Success}, + new object[] {@"//aa/bb../", true, false, @"\\aa/bb..", 9, Result.Success}, + new object[] {@"//aa/bb..//..", true, false, @"\\aa/bb..", 9, Result.Success}, + new object[] {@"/\\aa/bb/cc/..", true, true, @"/\\aa/bb", 8, Result.Success}, + new object[] {@"/\\aa/bb/cc/../..", true, true, @"/\\aa/bb", 8, Result.Success}, + new object[] {@"/\\aa/bb/cc/../../..", true, true, @"/\\aa/bb", 8, Result.Success}, + new object[] {@"/\\aa/bb/cc/../../../..", true, true, @"/\\aa/bb", 8, Result.Success}, + new object[] {@"c:aa\bb/cc", true, false, @"c:aa\bb/cc", 10, Result.Success}, + new object[] {@"c:aa\bb/cc/..", true, false, @"c:aa\bb", 7, Result.Success}, + new object[] {@"c:aa\bb/cc/../..", true, false, @"c:aa", 4, Result.Success}, + new object[] {@"c:aa\bb/cc/../../..", true, false, @"c:aa", 4, Result.Success}, + new object[] {@"c:\//\aa\bb", true, false, @"c:\/\aa\bb", 10, Result.Success}, + new object[] {@"c:\//\aa\bb/..", true, false, @"c:\", 3, Result.Success}, + new object[] {@"mount://////a/bb/c", true, true, @"mount:/\\a/bb/c", 15, Result.Success}, + new object[] {@"mount://////a/bb/c/..", true, true, @"mount:/\\a/bb", 13, Result.Success}, + new object[] {@"mount://////a/bb/c/../..", true, true, @"mount:/\\a/bb", 13, Result.Success}, + new object[] {@"//", true, false, @"/", 1, Result.Success}, + new object[] {@"///..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//a", true, false, @"/a", 2, Result.Success}, + new object[] {@"//a/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//a", true, false, @"/a", 2, Result.Success}, + new object[] {@"//a/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//a/", true, false, @"\\a/", 4, Result.Success}, + new object[] {@"//a//..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//a/b", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"//a/b/..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"//a/b/", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"//a/b//..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"//a/b/c", true, false, @"\\a/b/c", 7, Result.Success}, + new object[] {@"//a/b/c/..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"//a/b/c/../..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"//a/b/c/", true, false, @"\\a/b/c", 7, Result.Success}, + new object[] {@"//a/b/c//..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"//a/b/c//../..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\", true, false, @"\\", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a", true, false, @"\\", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/", true, false, @"\\a/", 4, Result.Success}, + new object[] {@"\\a//..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a/b/..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a/b/", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a/b//..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a/b/c", true, false, @"\\a/b/c", 7, Result.Success}, + new object[] {@"\\a/b/c/..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a/b/c/../..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a/b/c/", true, false, @"\\a/b/c", 7, Result.Success}, + new object[] {@"\\a/b/c//..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a/b/c//../..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\", true, false, @"\\", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a", true, false, @"\\", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\", true, false, @"\\a/", 4, Result.Success}, + new object[] {@"\\a\/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a\b/..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a\b\", true, false, @"\\a/b\", 6, Result.Success}, + new object[] {@"\\a\b\/..", true, false, @"\\a", 3, Result.Success}, + new object[] {@"\\a\b\c", true, false, @"\\a/b\c", 7, Result.Success}, + new object[] {@"\\a\b\c/..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a\b\c/../..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a\b\c\", true, false, @"\\a/b\c\", 8, Result.Success}, + new object[] {@"\\a\b\c\/..", true, false, @"\\a/b", 5, Result.Success}, + new object[] {@"\\a\b\c\/../..", true, false, @"\\a/b", 5, Result.Success} + }; + + public static object[][] IsNormalizedTestItems = + { + new object[] {@"", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"/", false, false, true, Result.Success}, + new object[] {@"/.", false, false, false, Result.Success}, + new object[] {@"/a/b/c", false, false, true, Result.Success}, + new object[] {@"/a/b/../c", false, false, false, Result.Success}, + new object[] {@"/a/b/c/..", false, false, false, Result.Success}, + new object[] {@"/a/b/c/.", false, false, false, Result.Success}, + new object[] {@"/a/../../..", false, false, false, Result.Success}, + new object[] {@"/a/../../../a/b/c", false, false, false, Result.Success}, + new object[] {@"//a/b//.//c", false, false, false, Result.Success}, + new object[] {@"/../a/b/c/.", false, false, false, Result.Success}, + new object[] {@"/./aaa/bbb/ccc/.", false, false, false, Result.Success}, + new object[] {@"/a/b/c/", false, false, false, Result.Success}, + new object[] {@"a/b/c/", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"/aa/./bb/../cc/", false, false, false, Result.Success}, + new object[] {@"/./b/../c/", false, false, false, Result.Success}, + new object[] {@"/a/../../../", false, false, false, Result.Success}, + new object[] {@"//a/b//.//c/", false, false, false, Result.Success}, + new object[] {@"/tmp/../", false, false, false, Result.Success}, + new object[] {@"a", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"a/../../../a/b/c", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"./b/../c/", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@".", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"../a/b/c/.", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"./a/b/c/.", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"abc", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:/a/b/../c", false, true, false, Result.Success}, + new object[] {@"a:/a/b/c", false, true, true, Result.Success}, + new object[] {@"mount:/a/b/../c", false, true, false, Result.Success}, + new object[] {@"mount:\a/b/../c", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:\a/b\../c", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:\a/b/c", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:/a\../b\..c", false, true, false, Result.Success}, + new object[] {@"mount:/a\../b/..cd", false, true, false, Result.Success}, + new object[] {@"mount:/a\..d/b/c\..", false, true, false, Result.Success}, + new object[] {@"mount:", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"abc:/a/../../../a/b/c", false, true, false, Result.Success}, + new object[] {@"abc:/./b/../c/", false, true, false, Result.Success}, + new object[] {@"abc:/.", false, true, false, Result.Success}, + new object[] {@"abc:/..", false, true, false, Result.Success}, + new object[] {@"abc:/", false, true, true, Result.Success}, + new object[] {@"abc://a/b//.//c", false, true, false, Result.Success}, + new object[] {@"abc:/././/././a/b//.//c", false, true, false, Result.Success}, + new object[] {@"mount:/d./aa", false, true, true, Result.Success}, + new object[] {@"mount:/d/..", false, true, false, Result.Success}, + new object[] {@"/path/aaa/bbb\..\h/ddd", false, false, false, Result.Success}, + new object[] {@"/path/aaa/bbb/../h/ddd", false, false, false, Result.Success}, + new object[] {@"/path/aaa/bbb\.\h/ddd", false, false, true, Result.Success}, + new object[] {@"/path/aaa/bbb\./h/ddd", false, false, true, Result.Success}, + new object[] {@"/path/aaa/bbb/./h/ddd", false, false, false, Result.Success}, + new object[] {@"mount:abcd", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:/", false, true, true, Result.Success}, + new object[] {@"mount:\..", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:/a/b\..", false, true, false, Result.Success}, + new object[] {@"mount:/dir", false, true, true, Result.Success}, + new object[] {@"mount:/dir/", false, true, false, Result.Success}, + new object[] {@"mount:\", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mo.unt:\", false, true, false, ResultFs.InvalidCharacter.Value}, + new object[] {@"mount.:\", false, true, false, ResultFs.InvalidCharacter.Value}, + new object[] {@"mount:./aa/bb", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:.../aa/bb", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:...aa/bb", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"...aa/bb", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount01234567890/aa/bb", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount01234567890:/aa/bb", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount0123456789:/aa/bb", false, true, true, Result.Success}, + new object[] {@"mount012345678:/aa/bb", false, true, true, Result.Success}, + new object[] {@"mount:aa/..\bb", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:..\bb", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:/..\bb", false, true, false, Result.Success}, + new object[] {@"mount:/.\bb", false, true, true, Result.Success}, + new object[] {@"mount:\..\bb", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:\.\bb", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:/a\..\bb", false, true, false, Result.Success}, + new object[] {@"mount:/a\.\bb", false, true, true, Result.Success}, + new object[] {@"//$abc/bb", false, false, false, Result.Success}, + new object[] {@"//:abc/bb", false, false, false, Result.Success}, + new object[] {@"\\\asd", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\/asd", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\//asd", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//", false, false, false, Result.Success}, + new object[] {@"///..", false, false, false, Result.Success}, + new object[] {@"\\a/b/cc/../d", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/cc/../d/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/cc/../d/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/cc/../d/../../..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:/aa/bb", false, true, true, Result.Success}, + new object[] {@"c:/aa/bb/..", false, true, false, Result.Success}, + new object[] {@"c:/aa/bb/../..", false, true, false, Result.Success}, + new object[] {@"c:/aa/bb/../../..", false, true, false, Result.Success}, + new object[] {@"mount:\c:/aa", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:\c:/aa/..", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:\c:/aa/../..", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:\c:/aa/../../..", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:/c:\aa/bb", false, true, true, Result.Success}, + new object[] {@"mount:/c:\aa/bb/..", false, true, false, Result.Success}, + new object[] {@"mount:/c:\aa/bb/../..", false, true, false, Result.Success}, + new object[] {@"mount:/c:\aa/bb/../../..", false, true, false, Result.Success}, + new object[] {@"mount:////c:\aa/bb", false, true, false, Result.Success}, + new object[] {@"mount:////c:\aa/bb/..", false, true, false, Result.Success}, + new object[] {@"mount:////c:\aa/bb/../..", false, true, false, Result.Success}, + new object[] {@"mount:////c:\aa/bb/../../..", false, true, false, Result.Success}, + new object[] {@"mount:/\aa/bb", false, true, true, Result.Success}, + new object[] {@"mount:/\aa/bb/..", false, true, false, Result.Success}, + new object[] {@"mount:/\aa/bb/../..", false, true, false, Result.Success}, + new object[] {@"mount:/\aa/bb/../../..", false, true, false, Result.Success}, + new object[] {@"mount:/c:/aa/bb", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:c:/aa/bb", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:c:/aa/bb", false, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:/\aa/../b", false, true, false, Result.Success}, + new object[] {@"mount:/\aa/../b/..", false, true, false, Result.Success}, + new object[] {@"mount:/\aa/../b/../..", false, true, false, Result.Success}, + new object[] {@"mount://aa/bb", false, true, false, Result.Success}, + new object[] {@"mount://aa/bb/..", false, true, false, Result.Success}, + new object[] {@"//aa/bb", false, true, false, Result.Success}, + new object[] {@"//aa/bb/..", false, true, false, Result.Success}, + new object[] {@"//aa/bb", false, false, false, Result.Success}, + new object[] {@"//aa/bb/..", false, false, false, Result.Success}, + new object[] {@"/aa/bb", false, false, true, Result.Success}, + new object[] {@"/aa/bb/..", false, false, false, Result.Success}, + new object[] {@"/aa/bb/../..", false, false, false, Result.Success}, + new object[] {@"/aa/bb/../../..", false, false, false, Result.Success}, + new object[] {@"c:/aa", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:/aa/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:/aa/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde/aa/bb", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde/aa/bb/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde/aa/bb/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde/aa/bb/../../..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:abcde/", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"///aa", false, false, false, Result.Success}, + new object[] {@"//aa//bb", false, false, false, Result.Success}, + new object[] {@"//aa//bb/..", false, false, false, Result.Success}, + new object[] {@"//./bb", false, false, false, Result.Success}, + new object[] {@"//../bb", false, false, false, Result.Success}, + new object[] {@"//.../bb", false, false, false, Result.Success}, + new object[] {@"//aa$abc/bb", false, false, false, Result.Success}, + new object[] {@"//aa$/bb", false, false, false, Result.Success}, + new object[] {@"//aa:/bb", false, false, false, Result.Success}, + new object[] {@"//aa/bb$b/cc$", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc$c", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc$c/..", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc$c/dd", false, false, false, Result.Success}, + new object[] {@"//aa/bb", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc//dd", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc//dd/..", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc//dd/../..", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc//dd/../../..", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc\/dd", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc//dd", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/dd", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/dd/..", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/dd/../..", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/dd/../../..", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/\dd", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/\dd/..", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/\dd/../..", false, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/\dd/../../..", false, false, false, Result.Success}, + new object[] {@"//aa/../", false, false, false, Result.Success}, + new object[] {@"//aa//", false, false, false, Result.Success}, + new object[] {@"//aa/bb..", false, false, false, Result.Success}, + new object[] {@"//aa/bb../..", false, false, false, Result.Success}, + new object[] {@"//aa/bb../", false, false, false, Result.Success}, + new object[] {@"//aa/bb..//..", false, false, false, Result.Success}, + new object[] {@"/\\aa/bb/cc/..", false, true, false, Result.Success}, + new object[] {@"/\\aa/bb/cc/../..", false, true, false, Result.Success}, + new object[] {@"/\\aa/bb/cc/../../..", false, true, false, Result.Success}, + new object[] {@"/\\aa/bb/cc/../../../..", false, true, false, Result.Success}, + new object[] {@"c:aa\bb/cc", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:aa\bb/cc/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:aa\bb/cc/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:aa\bb/cc/../../..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:\//\aa\bb", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"c:\//\aa\bb/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount://////a/bb/c", false, true, false, Result.Success}, + new object[] {@"mount://////a/bb/c/..", false, true, false, Result.Success}, + new object[] {@"mount://////a/bb/c/../..", false, true, false, Result.Success}, + new object[] {@"//", false, false, false, Result.Success}, + new object[] {@"///..", false, false, false, Result.Success}, + new object[] {@"//a", false, false, false, Result.Success}, + new object[] {@"//a/..", false, false, false, Result.Success}, + new object[] {@"//a", false, false, false, Result.Success}, + new object[] {@"//a/..", false, false, false, Result.Success}, + new object[] {@"//a/", false, false, false, Result.Success}, + new object[] {@"//a//..", false, false, false, Result.Success}, + new object[] {@"//a/b", false, false, false, Result.Success}, + new object[] {@"//a/b/..", false, false, false, Result.Success}, + new object[] {@"//a/b/", false, false, false, Result.Success}, + new object[] {@"//a/b//..", false, false, false, Result.Success}, + new object[] {@"//a/b/c", false, false, false, Result.Success}, + new object[] {@"//a/b/c/..", false, false, false, Result.Success}, + new object[] {@"//a/b/c/../..", false, false, false, Result.Success}, + new object[] {@"//a/b/c/", false, false, false, Result.Success}, + new object[] {@"//a/b/c//..", false, false, false, Result.Success}, + new object[] {@"//a/b/c//../..", false, false, false, Result.Success}, + new object[] {@"\\", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a//..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b//..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/c", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/c/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/c/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/c/", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/c//..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/c//../..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\c", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\c/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\c/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\c\", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\c\/..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\b\c\/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//$abc/bb", true, false, false, ResultFs.InvalidCharacter.Value}, + new object[] {@"//:abc/bb", true, false, false, ResultFs.InvalidCharacter.Value}, + new object[] {@"\\\asd", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\/asd", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\//asd", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//", true, false, false, Result.Success}, + new object[] {@"///..", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b/cc/../d", true, false, false, Result.Success}, + new object[] {@"\\a/b/cc/../d/..", true, false, false, Result.Success}, + new object[] {@"\\a/b/cc/../d/../..", true, false, false, Result.Success}, + new object[] {@"\\a/b/cc/../d/../../..", true, false, false, Result.Success}, + new object[] {@"c:/aa/bb", true, true, true, Result.Success}, + new object[] {@"c:/aa/bb/..", true, true, false, Result.Success}, + new object[] {@"c:/aa/bb/../..", true, true, false, Result.Success}, + new object[] {@"c:/aa/bb/../../..", true, true, false, Result.Success}, + new object[] {@"mount:\c:/aa", true, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:\c:/aa/..", true, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:\c:/aa/../..", true, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:\c:/aa/../../..", true, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:/c:\aa/bb", true, true, true, Result.Success}, + new object[] {@"mount:/c:\aa/bb/..", true, true, false, Result.Success}, + new object[] {@"mount:/c:\aa/bb/../..", true, true, false, Result.Success}, + new object[] {@"mount:/c:\aa/bb/../../..", true, true, false, Result.Success}, + new object[] {@"mount:////c:\aa/bb", true, true, false, Result.Success}, + new object[] {@"mount:////c:\aa/bb/..", true, true, false, Result.Success}, + new object[] {@"mount:////c:\aa/bb/../..", true, true, false, Result.Success}, + new object[] {@"mount:////c:\aa/bb/../../..", true, true, false, Result.Success}, + new object[] {@"mount:/\aa/bb", true, true, true, Result.Success}, + new object[] {@"mount:/\aa/bb/..", true, true, false, Result.Success}, + new object[] {@"mount:/\aa/bb/../..", true, true, false, Result.Success}, + new object[] {@"mount:/\aa/bb/../../..", true, true, false, Result.Success}, + new object[] {@"mount:/c:/aa/bb", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:c:/aa/bb", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:c:/aa/bb", true, true, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"mount:/\aa/../b", true, true, false, Result.Success}, + new object[] {@"mount:/\aa/../b/..", true, true, false, Result.Success}, + new object[] {@"mount:/\aa/../b/../..", true, true, false, Result.Success}, + new object[] {@"mount://aa/bb", true, true, false, Result.Success}, + new object[] {@"mount://aa/bb/..", true, true, false, Result.Success}, + new object[] {@"//aa/bb", true, true, false, Result.Success}, + new object[] {@"//aa/bb/..", true, true, false, Result.Success}, + new object[] {@"//aa/bb", true, false, false, Result.Success}, + new object[] {@"//aa/bb/..", true, false, false, Result.Success}, + new object[] {@"/aa/bb", true, false, true, Result.Success}, + new object[] {@"/aa/bb/..", true, false, false, Result.Success}, + new object[] {@"/aa/bb/../..", true, false, false, Result.Success}, + new object[] {@"/aa/bb/../../..", true, false, false, Result.Success}, + new object[] {@"c:/aa", true, false, true, Result.Success}, + new object[] {@"c:/aa/..", true, false, false, Result.Success}, + new object[] {@"c:/aa/../..", true, false, false, Result.Success}, + new object[] {@"c:abcde/aa/bb", true, false, true, Result.Success}, + new object[] {@"c:abcde/aa/bb/..", true, false, false, Result.Success}, + new object[] {@"c:abcde/aa/bb/../..", true, false, false, Result.Success}, + new object[] {@"c:abcde/aa/bb/../../..", true, false, false, Result.Success}, + new object[] {@"c:abcde", true, false, true, Result.Success}, + new object[] {@"c:abcde/..", true, false, false, Result.Success}, + new object[] {@"c:abcde/", true, false, false, Result.Success}, + new object[] {@"///aa", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//aa//bb", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//aa//bb/..", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//./bb", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//../bb", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//.../bb", true, false, false, Result.Success}, + new object[] {@"//aa$abc/bb", true, false, false, ResultFs.InvalidCharacter.Value}, + new object[] {@"//aa$/bb", true, false, false, ResultFs.InvalidCharacter.Value}, + new object[] {@"//aa:/bb", true, false, false, ResultFs.InvalidCharacter.Value}, + new object[] {@"//aa/bb$b/cc$", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//aa/bb/cc$c", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc$c/..", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc$c/dd", true, false, false, Result.Success}, + new object[] {@"//aa/bb", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc//dd", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc//dd/..", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc//dd/../..", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc//dd/../../..", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc\/dd", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc//dd", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/dd", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/dd/..", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/dd/../..", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/dd/../../..", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/\dd", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/\dd/..", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/\dd/../..", true, false, false, Result.Success}, + new object[] {@"//aa/bb/cc/\dd/../../..", true, false, false, Result.Success}, + new object[] {@"//aa/../", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//aa//", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//aa/bb..", true, false, false, Result.Success}, + new object[] {@"//aa/bb../..", true, false, false, Result.Success}, + new object[] {@"//aa/bb../", true, false, false, Result.Success}, + new object[] {@"//aa/bb..//..", true, false, false, Result.Success}, + new object[] {@"/\\aa/bb/cc/..", true, true, false, Result.Success}, + new object[] {@"/\\aa/bb/cc/../..", true, true, false, Result.Success}, + new object[] {@"/\\aa/bb/cc/../../..", true, true, false, Result.Success}, + new object[] {@"/\\aa/bb/cc/../../../..", true, true, false, Result.Success}, + new object[] {@"c:aa\bb/cc", true, false, true, Result.Success}, + new object[] {@"c:aa\bb/cc/..", true, false, false, Result.Success}, + new object[] {@"c:aa\bb/cc/../..", true, false, false, Result.Success}, + new object[] {@"c:aa\bb/cc/../../..", true, false, false, Result.Success}, + new object[] {@"c:\//\aa\bb", true, false, false, Result.Success}, + new object[] {@"c:\//\aa\bb/..", true, false, false, Result.Success}, + new object[] {@"mount://////a/bb/c", true, true, false, Result.Success}, + new object[] {@"mount://////a/bb/c/..", true, true, false, Result.Success}, + new object[] {@"mount://////a/bb/c/../..", true, true, false, Result.Success}, + new object[] {@"//", true, false, false, Result.Success}, + new object[] {@"///..", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//a", true, false, false, Result.Success}, + new object[] {@"//a/..", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//a", true, false, false, Result.Success}, + new object[] {@"//a/..", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//a/", true, false, false, Result.Success}, + new object[] {@"//a//..", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"//a/b", true, false, false, Result.Success}, + new object[] {@"//a/b/..", true, false, false, Result.Success}, + new object[] {@"//a/b/", true, false, false, Result.Success}, + new object[] {@"//a/b//..", true, false, false, Result.Success}, + new object[] {@"//a/b/c", true, false, false, Result.Success}, + new object[] {@"//a/b/c/..", true, false, false, Result.Success}, + new object[] {@"//a/b/c/../..", true, false, false, Result.Success}, + new object[] {@"//a/b/c/", true, false, false, Result.Success}, + new object[] {@"//a/b/c//..", true, false, false, Result.Success}, + new object[] {@"//a/b/c//../..", true, false, false, Result.Success}, + new object[] {@"\\", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\/..", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/..", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/", true, false, true, Result.Success}, + new object[] {@"\\a//..", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/b", true, false, true, Result.Success}, + new object[] {@"\\a/b/..", true, false, false, Result.Success}, + new object[] {@"\\a/b/", true, false, false, Result.Success}, + new object[] {@"\\a/b//..", true, false, false, Result.Success}, + new object[] {@"\\a/b/c", true, false, true, Result.Success}, + new object[] {@"\\a/b/c/..", true, false, false, Result.Success}, + new object[] {@"\\a/b/c/../..", true, false, false, Result.Success}, + new object[] {@"\\a/b/c/", true, false, false, Result.Success}, + new object[] {@"\\a/b/c//..", true, false, false, Result.Success}, + new object[] {@"\\a/b/c//../..", true, false, false, Result.Success}, + new object[] {@"\\", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\/..", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a/..", true, false, false, ResultFs.InvalidPathFormat.Value}, + new object[] {@"\\a\", true, false, false, Result.Success}, + new object[] {@"\\a\/..", true, false, false, Result.Success}, + new object[] {@"\\a\b", true, false, false, Result.Success}, + new object[] {@"\\a\b/..", true, false, false, Result.Success}, + new object[] {@"\\a\b\", true, false, false, Result.Success}, + new object[] {@"\\a\b\/..", true, false, false, Result.Success}, + new object[] {@"\\a\b\c", true, false, false, Result.Success}, + new object[] {@"\\a\b\c/..", true, false, false, Result.Success}, + new object[] {@"\\a\b\c/../..", true, false, false, Result.Success}, + new object[] {@"\\a\b\c\", true, false, false, Result.Success}, + new object[] {@"\\a\b\c\/..", true, false, false, Result.Success}, + new object[] {@"\\a\b\c\/../..", true, false, false, Result.Success} + }; + } +} diff --git a/tests/LibHac.Tests/LibHacTestFramework.cs b/tests/LibHac.Tests/LibHacTestFramework.cs new file mode 100644 index 00000000..f9c03b66 --- /dev/null +++ b/tests/LibHac.Tests/LibHacTestFramework.cs @@ -0,0 +1,30 @@ +using System.Diagnostics; +using LibHac.Tests; +using Xunit.Abstractions; +using Xunit.Sdk; + +[assembly: Xunit.TestFramework("LibHac.Tests." + nameof(LibHacTestFramework), "LibHac.Tests")] + +namespace LibHac.Tests +{ + public class LibHacTestFramework : XunitTestFramework + { + public LibHacTestFramework(IMessageSink messageSink) + : base(messageSink) + { + SetDebugHandler(); + SetResultNames(); + } + + private static void SetDebugHandler() + { + Trace.Listeners.Clear(); + Trace.Listeners.Add(new DebugAssertHandler()); + } + + private static void SetResultNames() + { + Result.SetNameResolver(new ResultNameResolver()); + } + } +} diff --git a/tests/LibHac.Tests/ResultNameResolver.cs b/tests/LibHac.Tests/ResultNameResolver.cs new file mode 100644 index 00000000..3991d1a5 --- /dev/null +++ b/tests/LibHac.Tests/ResultNameResolver.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace LibHac.Tests +{ + internal class ResultNameResolver : Result.IResultNameResolver + { + private Lazy> ResultNames { get; } = new Lazy>(GetResultNames); + + public bool TryResolveName(Result result, out string name) + { + return ResultNames.Value.TryGetValue(result, out name); + } + + private static Dictionary GetResultNames() + { + var dict = new Dictionary(); + + Assembly assembly = typeof(Result).Assembly; + + foreach (TypeInfo type in assembly.DefinedTypes.Where(x => x.Name.Contains("Result"))) + foreach (PropertyInfo property in type.DeclaredProperties + .Where(x => x.PropertyType == typeof(Result.Base) && x.GetMethod.IsStatic && x.SetMethod == null)) + { + Result value = ((Result.Base)property.GetValue(null, null)).Value; + string name = $"{type.Name}{property.Name}"; + + dict[value] = name; + } + + return dict; + } + } +}