Implement Nintendo's path normalization functions

This commit is contained in:
Alex Barney 2020-02-09 00:25:21 -07:00
parent ac3c496018
commit bc11d7ceaf
14 changed files with 2025 additions and 9 deletions

View File

@ -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<byte> 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;
}
}

View File

@ -46,6 +46,6 @@ namespace LibHac.Common
return StringUtils.Utf8ZToString(_buffer);
}
public bool IsNull() => _buffer == null;
public bool IsEmpty() => _buffer == null || _buffer.Length == 0;
}
}

View File

@ -113,7 +113,7 @@ namespace LibHac.Fs
internal static Result GetMountName(ReadOnlySpan<char> path, out ReadOnlySpan<char> mountName, out ReadOnlySpan<char> 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++)
{

View File

@ -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();

731
src/LibHac/Fs/PathTool.cs Normal file
View File

@ -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<byte> 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<byte> 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<byte> 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)));
}
/// <summary>
/// Checks if a path begins with / or \ and contains any of these patterns:
/// "/..\", "\..\", "\../", "\..0" where '0' is the null terminator.
/// </summary>
[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<byte> 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<byte>.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<byte>.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<byte> 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<byte> 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
}
}
}

View File

@ -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]));
}
}
}

View File

@ -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;
}
}

View File

@ -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),
}
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -0,0 +1,279 @@
// Uses GLoat to run code in nnsdk https://github.com/h1k421/GLoat
#include <gloat.hpp>
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);
}

View File

@ -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}
};
}
}

View File

@ -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());
}
}
}

View File

@ -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<Dictionary<Result, string>> ResultNames { get; } = new Lazy<Dictionary<Result, string>>(GetResultNames);
public bool TryResolveName(Result result, out string name)
{
return ResultNames.Value.TryGetValue(result, out name);
}
private static Dictionary<Result, string> GetResultNames()
{
var dict = new Dictionary<Result, string>();
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;
}
}
}