Remove old path code

This commit is contained in:
Alex Barney 2021-08-05 12:59:57 -07:00
parent aad87ec845
commit 689549fed7
23 changed files with 725 additions and 3117 deletions

View File

@ -23,7 +23,7 @@ namespace LibHac.Fs.Common
{
Span<byte> pathBuffer = path.GetWriteBufferLength() != 0 ? path.GetWriteBuffer() : Span<byte>.Empty;
int windowsSkipLength = WindowsPath12.GetWindowsSkipLength(pathBuffer);
int windowsSkipLength = WindowsPath.GetWindowsSkipLength(pathBuffer);
_buffer = pathBuffer.Slice(windowsSkipLength);
if (windowsSkipLength != 0)

View File

@ -310,7 +310,7 @@ namespace LibHac.Fs
Result rc = Initialize(path);
if (rc.IsFailure()) return rc;
if (_string.At(0) != NullTerminator && !WindowsPath12.IsWindowsPath(_string, false) &&
if (_string.At(0) != NullTerminator && !WindowsPath.IsWindowsPath(_string, false) &&
_string.At(0) != DirectorySeparator)
{
var flags = new PathFlags();
@ -319,7 +319,7 @@ namespace LibHac.Fs
rc = Normalize(flags);
if (rc.IsFailure()) return rc;
}
else if (WindowsPath12.IsWindowsPath(_string, true))
else if (WindowsPath.IsWindowsPath(_string, true))
{
var flags = new PathFlags();
flags.AllowWindowsPath();
@ -329,7 +329,7 @@ namespace LibHac.Fs
}
else
{
rc = PathNormalizer12.IsNormalized(out _isNormalized, out _, _string);
rc = PathNormalizer.IsNormalized(out _isNormalized, out _, _string);
if (rc.IsFailure()) return rc;
}
@ -354,7 +354,7 @@ namespace LibHac.Fs
Result rc = Initialize(path, length);
if (rc.IsFailure()) return rc;
if (_string.At(0) != NullTerminator && !WindowsPath12.IsWindowsPath(_string, false) &&
if (_string.At(0) != NullTerminator && !WindowsPath.IsWindowsPath(_string, false) &&
_string.At(0) != DirectorySeparator)
{
var flags = new PathFlags();
@ -363,7 +363,7 @@ namespace LibHac.Fs
rc = Normalize(flags);
if (rc.IsFailure()) return rc;
}
else if (WindowsPath12.IsWindowsPath(_string, true))
else if (WindowsPath.IsWindowsPath(_string, true))
{
var flags = new PathFlags();
flags.AllowWindowsPath();
@ -373,7 +373,7 @@ namespace LibHac.Fs
}
else
{
rc = PathNormalizer12.IsNormalized(out _isNormalized, out _, _string);
rc = PathNormalizer.IsNormalized(out _isNormalized, out _, _string);
if (rc.IsFailure()) return rc;
}
@ -391,7 +391,7 @@ namespace LibHac.Fs
if (_writeBufferLength > 1)
{
PathUtility12.Replace(GetWriteBuffer().Slice(0, _writeBufferLength - 1), AltDirectorySeparator,
PathUtility.Replace(GetWriteBuffer().Slice(0, _writeBufferLength - 1), AltDirectorySeparator,
DirectorySeparator);
}
@ -468,7 +468,7 @@ namespace LibHac.Fs
if (parent.Length == 0 || parent[0] == NullTerminator)
return Result.Success;
if (WindowsPath12.IsWindowsPath(_string, false))
if (WindowsPath.IsWindowsPath(_string, false))
return ResultFs.NotImplemented.Log();
// Remove a trailing separator from the parent and a leading one from the child so we can
@ -631,6 +631,9 @@ namespace LibHac.Fs
if (childBytesCopied != childLength)
return ResultFs.UnexpectedInPathA.Log();
// Note: Nintendo does not reset the "_isNormalized" field on the Path.
// This can result in the field and the actual normalization state being out of sync.
return Result.Success;
}
finally
@ -755,10 +758,10 @@ namespace LibHac.Fs
int bufferLength = _writeBufferLength;
if (flags.IsRelativePathAllowed() && PathUtility12.IsPathRelative(_string))
if (flags.IsRelativePathAllowed() && PathUtility.IsPathRelative(_string))
bufferLength += 2;
if (flags.IsWindowsPathAllowed() && WindowsPath12.IsWindowsPath(_string, true))
if (flags.IsWindowsPathAllowed() && WindowsPath.IsWindowsPath(_string, true))
bufferLength += 1;
int alignedBufferLength = Alignment.AlignUpPow2(bufferLength, WriteBufferAlignmentLength);
@ -794,7 +797,7 @@ namespace LibHac.Fs
{
public static Result SetUpFixedPath(ref Path path, ReadOnlySpan<byte> pathBuffer)
{
Result rc = PathNormalizer12.IsNormalized(out bool isNormalized, out _, pathBuffer);
Result rc = PathNormalizer.IsNormalized(out bool isNormalized, out _, pathBuffer);
if (rc.IsFailure()) return rc;
if (!isNormalized)

View File

@ -11,6 +11,10 @@ using static LibHac.Fs.StringTraits;
// ReSharper disable once CheckNamespace
namespace LibHac.Fs
{
/// <summary>
/// Contains functions for working with path formatting and normalization.
/// </summary>
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
public static class PathFormatter
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -133,7 +137,7 @@ namespace LibHac.Fs
currentPath = path.Slice(1);
}
else if (path.Length != 0 && WindowsPath12.IsWindowsDrive(path.Slice(1)))
else if (path.Length != 0 && WindowsPath.IsWindowsDrive(path.Slice(1)))
{
if (normalizeBuffer.Length == 0)
return ResultFs.NotNormalized.Log();
@ -142,7 +146,7 @@ namespace LibHac.Fs
}
}
if (WindowsPath12.IsWindowsDrive(currentPath))
if (WindowsPath.IsWindowsDrive(currentPath))
{
int winPathLength;
for (winPathLength = 2; currentPath.At(winPathLength) != NullTerminator; winPathLength++)
@ -170,7 +174,7 @@ namespace LibHac.Fs
currentPath.Slice(0, winPathLength).CopyTo(normalizeBuffer);
normalizeBuffer[winPathLength] = NullTerminator;
PathUtility12.Replace(normalizeBuffer.Slice(0, winPathLength), AltDirectorySeparator,
PathUtility.Replace(normalizeBuffer.Slice(0, winPathLength), AltDirectorySeparator,
DirectorySeparator);
}
@ -179,11 +183,11 @@ namespace LibHac.Fs
return Result.Success;
}
if (WindowsPath12.IsDosDevicePath(currentPath))
if (WindowsPath.IsDosDevicePath(currentPath))
{
int dosPathLength = WindowsPath12.GetDosDevicePathPrefixLength();
int dosPathLength = WindowsPath.GetDosDevicePathPrefixLength();
if (WindowsPath12.IsWindowsDrive(currentPath.Slice(dosPathLength)))
if (WindowsPath.IsWindowsDrive(currentPath.Slice(dosPathLength)))
{
dosPathLength += 2;
}
@ -199,7 +203,7 @@ namespace LibHac.Fs
currentPath.Slice(0, dosPathLength).CopyTo(normalizeBuffer);
normalizeBuffer[dosPathLength] = NullTerminator;
PathUtility12.Replace(normalizeBuffer.Slice(0, dosPathLength), DirectorySeparator,
PathUtility.Replace(normalizeBuffer.Slice(0, dosPathLength), DirectorySeparator,
AltDirectorySeparator);
}
@ -208,7 +212,7 @@ namespace LibHac.Fs
return Result.Success;
}
if (WindowsPath12.IsUncPath(currentPath, false, true))
if (WindowsPath.IsUncPath(currentPath, false, true))
{
Result rc;
@ -274,7 +278,7 @@ namespace LibHac.Fs
currentPath.Slice(0, uncPrefixLength).CopyTo(normalizeBuffer);
normalizeBuffer[uncPrefixLength] = NullTerminator;
PathUtility12.Replace(normalizeBuffer.Slice(0, uncPrefixLength), DirectorySeparator, AltDirectorySeparator);
PathUtility.Replace(normalizeBuffer.Slice(0, uncPrefixLength), DirectorySeparator, AltDirectorySeparator);
}
newPath = finalPath;
@ -364,7 +368,7 @@ namespace LibHac.Fs
{
UnsafeHelpers.SkipParamInit(out isNormalized, out normalizedLength);
Result rc = PathUtility12.CheckUtf8(path);
Result rc = PathUtility.CheckUtf8(path);
if (rc.IsFailure()) return rc;
ReadOnlySpan<byte> buffer = path;
@ -388,7 +392,7 @@ namespace LibHac.Fs
return ResultFs.InvalidPathFormat.Log();
}
if (WindowsPath12.IsWindowsPath(path, false) && !flags.IsWindowsPathAllowed())
if (WindowsPath.IsWindowsPath(path, false) && !flags.IsWindowsPathAllowed())
return ResultFs.InvalidPathFormat.Log();
bool hasMountName = false;
@ -405,10 +409,10 @@ namespace LibHac.Fs
hasMountName = true;
}
if (buffer.At(0) != DirectorySeparator && !PathUtility12.IsPathStartWithCurrentDirectory(buffer) &&
!WindowsPath12.IsWindowsPath(buffer, false))
if (buffer.At(0) != DirectorySeparator && !PathUtility.IsPathStartWithCurrentDirectory(buffer) &&
!WindowsPath.IsWindowsPath(buffer, false))
{
if (!flags.IsRelativePathAllowed() || !PathUtility12.CheckInvalidCharacter(buffer.At(0)).IsSuccess())
if (!flags.IsRelativePathAllowed() || !PathUtility.CheckInvalidCharacter(buffer.At(0)).IsSuccess())
return ResultFs.InvalidPathFormat.Log();
isNormalized = false;
@ -475,10 +479,10 @@ namespace LibHac.Fs
}
}
if (PathNormalizer12.IsParentDirectoryPathReplacementNeeded(buffer))
if (PathNormalizer.IsParentDirectoryPathReplacementNeeded(buffer))
return ResultFs.DirectoryUnobtainable.Log();
rc = PathUtility12.CheckInvalidBackslash(out bool isBackslashContained, buffer,
rc = PathUtility.CheckInvalidBackslash(out bool isBackslashContained, buffer,
flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed());
if (rc.IsFailure()) return rc;
@ -488,7 +492,7 @@ namespace LibHac.Fs
return Result.Success;
}
rc = PathNormalizer12.IsNormalized(out isNormalized, out int length, buffer);
rc = PathNormalizer.IsNormalized(out isNormalized, out int length, buffer);
if (rc.IsFailure()) return rc;
totalLength += length;
@ -528,10 +532,10 @@ namespace LibHac.Fs
bool isDriveRelative = false;
if (src.At(0) != DirectorySeparator && !PathUtility12.IsPathStartWithCurrentDirectory(src) &&
!WindowsPath12.IsWindowsPath(src, false))
if (src.At(0) != DirectorySeparator && !PathUtility.IsPathStartWithCurrentDirectory(src) &&
!WindowsPath.IsWindowsPath(src, false))
{
if (!flags.IsRelativePathAllowed() || !PathUtility12.CheckInvalidCharacter(src.At(0)).IsSuccess())
if (!flags.IsRelativePathAllowed() || !PathUtility.CheckInvalidCharacter(src.At(0)).IsSuccess())
return ResultFs.InvalidPathFormat.Log();
outputBuffer[currentPos++] = Dot;
@ -589,7 +593,7 @@ namespace LibHac.Fs
isWindowsPath = true;
}
rc = PathUtility12.CheckInvalidBackslash(out bool isBackslashContained, src,
rc = PathUtility.CheckInvalidBackslash(out bool isBackslashContained, src,
flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed());
if (rc.IsFailure()) return rc;
@ -601,7 +605,7 @@ namespace LibHac.Fs
srcBufferSlashReplaced = ArrayPool<byte>.Shared.Rent(path.Length);
StringUtils.Copy(srcBufferSlashReplaced, path);
PathUtility12.Replace(srcBufferSlashReplaced, AltDirectorySeparator, DirectorySeparator);
PathUtility.Replace(srcBufferSlashReplaced, AltDirectorySeparator, DirectorySeparator);
int srcOffset = (int)Unsafe.ByteOffset(ref MemoryMarshal.GetReference(path),
ref MemoryMarshal.GetReference(src));
@ -609,7 +613,7 @@ namespace LibHac.Fs
src = srcBufferSlashReplaced.AsSpan(srcOffset);
}
rc = PathNormalizer12.Normalize(outputBuffer.Slice(currentPos), out _, src, isWindowsPath, isDriveRelative);
rc = PathNormalizer.Normalize(outputBuffer.Slice(currentPos), out _, src, isWindowsPath, isDriveRelative);
if (rc.IsFailure()) return rc;
return Result.Success;

File diff suppressed because it is too large Load Diff

View File

@ -1,368 +0,0 @@
using System;
using LibHac.Common;
using LibHac.Diag;
using LibHac.FsSystem;
using static LibHac.Fs.PathUtility12;
using static LibHac.Fs.StringTraits;
// ReSharper disable once CheckNamespace
namespace LibHac.Fs
{
public static class PathNormalizer12
{
private enum PathState
{
Initial,
Normal,
FirstSeparator,
Separator,
CurrentDir,
ParentDir
}
public static Result Normalize(Span<byte> outputBuffer, out int length, ReadOnlySpan<byte> path, bool isWindowsPath,
bool isDriveRelativePath)
{
UnsafeHelpers.SkipParamInit(out length);
ReadOnlySpan<byte> currentPath = path;
int totalLength = 0;
int i = 0;
if (!IsSeparator(path.At(0)))
{
if (!isDriveRelativePath)
return ResultFs.InvalidPathFormat.Log();
outputBuffer[totalLength++] = DirectorySeparator;
}
var convertedPath = new RentedArray<byte>();
try
{
// Check if parent directory path replacement is needed.
if (IsParentDirectoryPathReplacementNeeded(currentPath))
{
// Allocate a buffer to hold the replacement path.
convertedPath = new RentedArray<byte>(PathTools.MaxPathLength + 1);
// Replace the path.
ReplaceParentDirectoryPath(convertedPath.Span, currentPath);
// Set current path to be the replacement path.
currentPath = new U8Span(convertedPath.Span);
}
bool skipNextSeparator = false;
while (!IsNul(currentPath.At(i)))
{
if (IsSeparator(currentPath[i]))
{
do
{
i++;
} while (IsSeparator(currentPath.At(i)));
if (IsNul(currentPath.At(i)))
break;
if (!skipNextSeparator)
{
if (totalLength + 1 == outputBuffer.Length)
{
outputBuffer[totalLength] = NullTerminator;
length = totalLength;
return ResultFs.TooLongPath.Log();
}
outputBuffer[totalLength++] = DirectorySeparator;
}
skipNextSeparator = false;
}
int dirLen = 0;
while (!IsSeparator(currentPath.At(i + dirLen)) && !IsNul(currentPath.At(i + dirLen)))
{
dirLen++;
}
if (IsCurrentDirectory(currentPath.Slice(i)))
{
skipNextSeparator = true;
}
else if (IsParentDirectory(currentPath.Slice(i)))
{
Assert.SdkAssert(outputBuffer[totalLength - 1] == DirectorySeparator);
if (!isWindowsPath)
Assert.SdkAssert(outputBuffer[0] == DirectorySeparator);
if (totalLength == 1)
{
if (!isWindowsPath)
return ResultFs.DirectoryUnobtainable.Log();
totalLength--;
}
else
{
totalLength -= 2;
do
{
if (outputBuffer[totalLength] == DirectorySeparator)
break;
totalLength--;
} while (totalLength != 0);
}
if (!isWindowsPath)
Assert.SdkAssert(outputBuffer[totalLength] == DirectorySeparator);
Assert.SdkAssert(totalLength < outputBuffer.Length);
}
else
{
if (totalLength + dirLen + 1 > outputBuffer.Length)
{
int copyLen = outputBuffer.Length - 1 - totalLength;
for (int j = 0; j < copyLen; j++)
{
outputBuffer[totalLength++] = currentPath[i + j];
}
outputBuffer[totalLength] = NullTerminator;
length = totalLength;
return ResultFs.TooLongPath.Log();
}
for (int j = 0; j < dirLen; j++)
{
outputBuffer[totalLength++] = currentPath[i + j];
}
}
i += dirLen;
}
if (skipNextSeparator)
totalLength--;
if (totalLength == 0 && outputBuffer.Length != 0)
{
totalLength = 1;
outputBuffer[0] = DirectorySeparator;
}
// Note: This bug is in the original code. They probably meant to put "totalLength + 1"
if (totalLength - 1 > outputBuffer.Length)
return ResultFs.TooLongPath.Log();
outputBuffer[totalLength] = NullTerminator;
Result rc = IsNormalized(out bool isNormalized, out _, outputBuffer);
if (rc.IsFailure()) return rc;
Assert.SdkAssert(isNormalized);
length = totalLength;
return Result.Success;
}
finally
{
convertedPath.Dispose();
}
}
/// <summary>
/// Checks if a given path is normalized. Path must be a basic path, starting with a directory separator
/// and not containing any sort of prefix such as a mount name.
/// </summary>
/// <param name="isNormalized">When this function returns <see cref="Result.Success"/>,
/// contains <see langword="true"/> if the path is normalized or <see langword="false"/> if it is not.
/// Contents are undefined if the function does not return <see cref="Result.Success"/>.
/// </param>
/// <param name="length">When this function returns <see cref="Result.Success"/> and
/// <paramref name="isNormalized"/> is <see langword="true"/>, contains the length of the normalized path.
/// Contents are undefined if the function does not return <see cref="Result.Success"/>
/// or <paramref name="isNormalized"/> is <see langword="false"/>.
/// </param>
/// <param name="path">The path to check.</param>
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
/// <see cref="ResultFs.InvalidCharacter"/>: The path contains an invalid character.<br/>
/// <see cref="ResultFs.InvalidPathFormat"/>: The path is not in a valid format.</returns>
public static Result IsNormalized(out bool isNormalized, out int length, ReadOnlySpan<byte> path)
{
UnsafeHelpers.SkipParamInit(out isNormalized, out length);
var state = PathState.Initial;
int pathLength = 0;
for (int i = 0; i < path.Length; i++)
{
byte c = path[i];
if (c == NullTerminator) break;
pathLength++;
if (state != PathState.Initial)
{
Result rc = CheckInvalidCharacter(c);
if (rc.IsFailure()) return rc;
}
switch (state)
{
case PathState.Initial:
if (c != DirectorySeparator)
return ResultFs.InvalidPathFormat.Log();
state = PathState.FirstSeparator;
break;
case PathState.Normal:
if (c == DirectorySeparator)
state = PathState.Separator;
break;
case PathState.FirstSeparator:
case PathState.Separator:
if (c == DirectorySeparator)
{
isNormalized = false;
return Result.Success;
}
state = c == Dot ? PathState.CurrentDir : PathState.Normal;
break;
case PathState.CurrentDir:
if (c == DirectorySeparator)
{
isNormalized = false;
return Result.Success;
}
state = c == Dot ? PathState.ParentDir : PathState.Normal;
break;
case PathState.ParentDir:
if (c == DirectorySeparator)
{
isNormalized = false;
return Result.Success;
}
state = PathState.Normal;
break;
// ReSharper disable once UnreachableSwitchCaseDueToIntegerAnalysis
default:
Abort.UnexpectedDefault();
break;
}
}
switch (state)
{
case PathState.Initial:
return ResultFs.InvalidPathFormat.Log();
case PathState.Normal:
case PathState.FirstSeparator:
isNormalized = true;
break;
case PathState.Separator:
case PathState.CurrentDir:
case PathState.ParentDir:
isNormalized = false;
break;
// ReSharper disable once UnreachableSwitchCaseDueToIntegerAnalysis
default:
Abort.UnexpectedDefault();
break;
}
length = pathLength;
return Result.Success;
}
/// <summary>
/// Checks if a path begins with / or \ and contains any of these patterns:
/// "/..\", "\..\", "\../", "\..0" where '0' is the null terminator.
/// </summary>
public static bool IsParentDirectoryPathReplacementNeeded(ReadOnlySpan<byte> path)
{
if (path.Length == 0 || (path[0] != DirectorySeparator && path[0] != AltDirectorySeparator))
return false;
for (int i = 0; i < path.Length - 2 && path[i] != NullTerminator; i++)
{
byte c3 = path.At(i + 3);
if (path[i] == AltDirectorySeparator &&
path[i + 1] == Dot &&
path[i + 2] == Dot &&
(c3 == DirectorySeparator || c3 == AltDirectorySeparator || c3 == NullTerminator))
{
return true;
}
if ((path[i] == DirectorySeparator || path[i] == AltDirectorySeparator) &&
path[i + 1] == Dot &&
path[i + 2] == Dot &&
c3 == AltDirectorySeparator)
{
return true;
}
}
return false;
}
private static void ReplaceParentDirectoryPath(Span<byte> dest, ReadOnlySpan<byte> source)
{
dest[0] = DirectorySeparator;
int i = 1;
while (source.Length > i && source[i] != NullTerminator)
{
if (source.Length > i + 2 &&
(source[i - 1] == DirectorySeparator || source[i - 1] == AltDirectorySeparator) &&
source[i + 0] == Dot &&
source[i + 1] == Dot &&
(source[i + 2] == DirectorySeparator || source[i + 2] == AltDirectorySeparator))
{
dest[i - 1] = DirectorySeparator;
dest[i + 0] = Dot;
dest[i + 1] = Dot;
dest[i + 2] = DirectorySeparator;
i += 3;
}
else
{
if (source.Length > i + 1 &&
source[i - 1] == AltDirectorySeparator &&
source[i + 0] == Dot &&
source[i + 1] == Dot &&
(source.Length == i + 2 || source[i + 2] == NullTerminator))
{
dest[i - 1] = DirectorySeparator;
dest[i + 0] = Dot;
dest[i + 1] = Dot;
i += 2;
break;
}
dest[i] = source[i];
i++;
}
}
dest[i] = NullTerminator;
}
}
}

View File

@ -1,58 +1,13 @@
using System;
using System.Diagnostics;
using LibHac.Common;
using LibHac.Diag;
using LibHac.FsSrv.Sf;
using LibHac.Util;
using static LibHac.Fs.StringTraits;
// ReSharper disable once CheckNamespace
namespace LibHac.Fs
{
internal struct PathUtilityGlobals
{
public PathVerifier PathVerifier;
public void Initialize(FileSystemClient _)
{
PathVerifier.Initialize();
}
}
internal struct PathVerifier
{
public void Initialize()
{
// Todo
}
public static Result Verify(U8Span path, int maxPathLength, int maxNameLength)
{
Debug.Assert(!path.IsNull());
int nameLength = 0;
for (int i = 0; i < path.Length && i <= maxPathLength && nameLength <= maxNameLength; i++)
{
byte c = path[i];
if (c == 0)
return Result.Success;
// todo: Compare path based on their Unicode code points
if (c == ':' || c == '*' || c == '?' || c == '<' || c == '>' || c == '|')
return ResultFs.InvalidCharacter.Log();
nameLength++;
if (c == '\\' || c == '/')
{
nameLength = 0;
}
}
return ResultFs.TooLongPath.Log();
}
}
public static class PathUtility
{
public static void Replace(Span<byte> buffer, byte currentChar, byte newChar)
@ -68,62 +23,223 @@ namespace LibHac.Fs
}
}
/// <summary>
/// Performs the extra functions that nn::fs::FspPathPrintf does on the string buffer.
/// </summary>
/// <param name="builder">The string builder to process.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public static Result ToSfPath(in this U8StringBuilder builder)
public static bool IsCurrentDirectory(ReadOnlySpan<byte> path)
{
if (builder.Overflowed)
if (path.Length < 1)
return false;
return path[0] == Dot &&
(path.Length < 2 || path[1] == NullTerminator || path[1] == DirectorySeparator);
}
public static bool IsParentDirectory(ReadOnlySpan<byte> path)
{
if (path.Length < 2)
return false;
return path[0] == Dot &&
path[1] == Dot &&
(path.Length < 3 || path[2] == NullTerminator || path[2] == DirectorySeparator);
}
public static bool IsSeparator(byte c)
{
return c == DirectorySeparator;
}
public static bool IsNul(byte c)
{
return c == NullTerminator;
}
public static Result ConvertToFspPath(out FspPath fspPath, ReadOnlySpan<byte> path)
{
UnsafeHelpers.SkipParamInit(out fspPath);
int length = StringUtils.Copy(SpanHelpers.AsByteSpan(ref fspPath), path, PathTool.EntryNameLengthMax + 1);
if (length >= PathTool.EntryNameLengthMax + 1)
return ResultFs.TooLongPath.Log();
Replace(builder.Buffer.Slice(builder.Capacity),
AltDirectorySeparator,
DirectorySeparator);
Result rc = PathFormatter.SkipMountName(out ReadOnlySpan<byte> pathWithoutMountName, out _,
new U8Span(path));
if (rc.IsFailure()) return rc;
if (!WindowsPath.IsWindowsPath(pathWithoutMountName, true))
{
Replace(SpanHelpers.AsByteSpan(ref fspPath).Slice(0, 0x300), AltDirectorySeparator, DirectorySeparator);
}
else if (fspPath.Str[0] == DirectorySeparator && fspPath.Str[1] == DirectorySeparator)
{
SpanHelpers.AsByteSpan(ref fspPath)[0] = AltDirectorySeparator;
SpanHelpers.AsByteSpan(ref fspPath)[1] = AltDirectorySeparator;
}
return Result.Success;
}
public static Result VerifyPath(this FileSystemClient fs, U8Span path, int maxPathLength, int maxNameLength)
public static bool IsDirectoryPath(ReadOnlySpan<byte> path)
{
return PathVerifier.Verify(path, maxPathLength, maxNameLength);
}
public static bool IsSubPath(U8Span lhs, U8Span rhs)
{
Assert.SdkRequires(!lhs.IsNull());
Assert.SdkRequires(!rhs.IsNull());
bool isUncLhs = WindowsPath.IsUnc(lhs);
bool isUncRhs = WindowsPath.IsUnc(rhs);
if (isUncLhs && !isUncRhs || !isUncLhs && isUncRhs)
if (path.Length < 1 || path[0] == NullTerminator)
return false;
if (lhs.GetOrNull(0) == DirectorySeparator && lhs.GetOrNull(1) == NullTerminator &&
rhs.GetOrNull(0) == DirectorySeparator && rhs.GetOrNull(1) != NullTerminator)
int length = StringUtils.GetLength(path);
return path[length - 1] == DirectorySeparator || path[length - 1] == AltDirectorySeparator;
}
public static bool IsDirectoryPath(in FspPath path)
{
return IsDirectoryPath(SpanHelpers.AsReadOnlyByteSpan(in path));
}
public static Result CheckUtf8(ReadOnlySpan<byte> path)
{
Assert.SdkRequiresNotNull(path);
uint utf8Buffer = 0;
Span<byte> utf8BufferSpan = SpanHelpers.AsByteSpan(ref utf8Buffer);
ReadOnlySpan<byte> currentChar = path;
while (currentChar.Length > 0 && currentChar[0] != NullTerminator)
{
utf8BufferSpan.Clear();
CharacterEncodingResult result =
CharacterEncoding.PickOutCharacterFromUtf8String(utf8BufferSpan, ref currentChar);
if (result != CharacterEncodingResult.Success)
return ResultFs.InvalidPathFormat.Log();
result = CharacterEncoding.ConvertCharacterUtf8ToUtf32(out _, utf8BufferSpan);
if (result != CharacterEncodingResult.Success)
return ResultFs.InvalidPathFormat.Log();
}
return Result.Success;
}
public static Result CheckInvalidCharacter(byte c)
{
/*
The optimized code is equivalent to this:
ReadOnlySpan<byte> invalidChars = new[]
{(byte) ':', (byte) '*', (byte) '?', (byte) '<', (byte) '>', (byte) '|'};
for (int i = 0; i < invalidChars.Length; i++)
{
if (c == invalidChars[i])
return ResultFs.InvalidCharacter.Log();
}
return Result.Success;
*/
const ulong mask = (1ul << (byte)':') |
(1ul << (byte)'*') |
(1ul << (byte)'?') |
(1ul << (byte)'<') |
(1ul << (byte)'>');
if (c <= 0x3Fu && ((1ul << c) & mask) != 0 || c == (byte)'|')
return ResultFs.InvalidCharacter.Log();
return Result.Success;
}
public static Result CheckInvalidBackslash(out bool containsBackslash, ReadOnlySpan<byte> path, bool allowBackslash)
{
containsBackslash = false;
for (int i = 0; i < path.Length && path[i] != NullTerminator; i++)
{
if (path[i] == '\\')
{
containsBackslash = true;
if (!allowBackslash)
return ResultFs.InvalidCharacter.Log();
}
}
return Result.Success;
}
public static Result CheckEntryNameBytes(ReadOnlySpan<byte> path, int maxEntryLength)
{
Assert.SdkRequiresNotNull(path);
int currentEntryLength = 0;
for (int i = 0; i < path.Length && path[i] != NullTerminator; i++)
{
currentEntryLength++;
if (path[i] == DirectorySeparator || path[i] == AltDirectorySeparator)
currentEntryLength = 0;
// Note: The original does use >= instead of >
if (currentEntryLength >= maxEntryLength)
return ResultFs.TooLongPath.Log();
}
return Result.Success;
}
public static bool IsSubPath(ReadOnlySpan<byte> lhs, ReadOnlySpan<byte> rhs)
{
Assert.SdkRequiresNotNull(lhs);
Assert.SdkRequiresNotNull(rhs);
if (WindowsPath.IsUncPath(lhs) && !WindowsPath.IsUncPath(rhs))
return false;
if (!WindowsPath.IsUncPath(lhs) && WindowsPath.IsUncPath(rhs))
return false;
if (lhs.At(0) == DirectorySeparator && lhs.At(1) == NullTerminator &&
rhs.At(0) == DirectorySeparator && rhs.At(1) != NullTerminator)
return true;
if (rhs.GetOrNull(0) == DirectorySeparator && rhs.GetOrNull(1) == NullTerminator &&
lhs.GetOrNull(0) == DirectorySeparator && lhs.GetOrNull(1) != NullTerminator)
if (rhs.At(0) == DirectorySeparator && rhs.At(1) == NullTerminator &&
lhs.At(0) == DirectorySeparator && lhs.At(1) != NullTerminator)
return true;
for (int i = 0; ; i++)
{
if (lhs.GetOrNull(i) == NullTerminator)
if (lhs.At(i) == NullTerminator)
{
return rhs.GetOrNull(i) == DirectorySeparator;
return rhs.At(i) == DirectorySeparator;
}
else if (rhs.GetOrNull(i) == NullTerminator)
else if (rhs.At(i) == NullTerminator)
{
return lhs.GetOrNull(i) == DirectorySeparator;
return lhs.At(i) == DirectorySeparator;
}
else if (lhs.GetOrNull(i) != rhs.GetOrNull(i))
else if (lhs.At(i) != rhs.At(i))
{
return false;
}
}
}
public static bool IsPathAbsolute(ReadOnlySpan<byte> path)
{
if (WindowsPath.IsWindowsPath(path, false))
return true;
return path.At(0) == DirectorySeparator;
}
public static bool IsPathRelative(ReadOnlySpan<byte> path)
{
return path.At(0) != NullTerminator && !IsPathAbsolute(path);
}
public static bool IsPathStartWithCurrentDirectory(ReadOnlySpan<byte> path)
{
return IsCurrentDirectory(path) || IsParentDirectory(path);
}
}
}

View File

@ -1,245 +0,0 @@
using System;
using LibHac.Common;
using LibHac.Diag;
using LibHac.FsSrv.Sf;
using LibHac.Util;
using static LibHac.Fs.StringTraits;
// ReSharper disable once CheckNamespace
namespace LibHac.Fs
{
public static class PathUtility12
{
public static void Replace(Span<byte> buffer, byte currentChar, byte newChar)
{
Assert.SdkRequiresNotNull(buffer);
for (int i = 0; i < buffer.Length; i++)
{
if (buffer[i] == currentChar)
{
buffer[i] = newChar;
}
}
}
public static bool IsCurrentDirectory(ReadOnlySpan<byte> path)
{
if (path.Length < 1)
return false;
return path[0] == Dot &&
(path.Length < 2 || path[1] == NullTerminator || path[1] == DirectorySeparator);
}
public static bool IsParentDirectory(ReadOnlySpan<byte> path)
{
if (path.Length < 2)
return false;
return path[0] == Dot &&
path[1] == Dot &&
(path.Length < 3 || path[2] == NullTerminator || path[2] == DirectorySeparator);
}
public static bool IsSeparator(byte c)
{
return c == DirectorySeparator;
}
public static bool IsNul(byte c)
{
return c == NullTerminator;
}
public static Result ConvertToFspPath(out FspPath fspPath, ReadOnlySpan<byte> path)
{
UnsafeHelpers.SkipParamInit(out fspPath);
int length = StringUtils.Copy(SpanHelpers.AsByteSpan(ref fspPath), path, PathTool.EntryNameLengthMax + 1);
if (length >= PathTool.EntryNameLengthMax + 1)
return ResultFs.TooLongPath.Log();
Result rc = PathFormatter.SkipMountName(out ReadOnlySpan<byte> pathWithoutMountName, out _,
new U8Span(path));
if (rc.IsFailure()) return rc;
if (!WindowsPath12.IsWindowsPath(pathWithoutMountName, true))
{
Replace(SpanHelpers.AsByteSpan(ref fspPath).Slice(0, 0x300), AltDirectorySeparator, DirectorySeparator);
}
else if (fspPath.Str[0] == DirectorySeparator && fspPath.Str[1] == DirectorySeparator)
{
SpanHelpers.AsByteSpan(ref fspPath)[0] = AltDirectorySeparator;
SpanHelpers.AsByteSpan(ref fspPath)[1] = AltDirectorySeparator;
}
return Result.Success;
}
public static bool IsDirectoryPath(ReadOnlySpan<byte> path)
{
if (path.Length < 1 || path[0] == NullTerminator)
return false;
int length = StringUtils.GetLength(path);
return path[length - 1] == DirectorySeparator || path[length - 1] == AltDirectorySeparator;
}
public static bool IsDirectoryPath(in FspPath path)
{
return IsDirectoryPath(SpanHelpers.AsReadOnlyByteSpan(in path));
}
public static Result CheckUtf8(ReadOnlySpan<byte> path)
{
Assert.SdkRequiresNotNull(path);
uint utf8Buffer = 0;
Span<byte> utf8BufferSpan = SpanHelpers.AsByteSpan(ref utf8Buffer);
ReadOnlySpan<byte> currentChar = path;
while (currentChar.Length > 0 && currentChar[0] != NullTerminator)
{
utf8BufferSpan.Clear();
CharacterEncodingResult result =
CharacterEncoding.PickOutCharacterFromUtf8String(utf8BufferSpan, ref currentChar);
if (result != CharacterEncodingResult.Success)
return ResultFs.InvalidPathFormat.Log();
result = CharacterEncoding.ConvertCharacterUtf8ToUtf32(out _, utf8BufferSpan);
if (result != CharacterEncodingResult.Success)
return ResultFs.InvalidPathFormat.Log();
}
return Result.Success;
}
public static Result CheckInvalidCharacter(byte c)
{
/*
The optimized code is equivalent to this:
ReadOnlySpan<byte> invalidChars = new[]
{(byte) ':', (byte) '*', (byte) '?', (byte) '<', (byte) '>', (byte) '|'};
for (int i = 0; i < invalidChars.Length; i++)
{
if (c == invalidChars[i])
return ResultFs.InvalidCharacter.Log();
}
return Result.Success;
*/
const ulong mask = (1ul << (byte)':') |
(1ul << (byte)'*') |
(1ul << (byte)'?') |
(1ul << (byte)'<') |
(1ul << (byte)'>');
if (c <= 0x3Fu && ((1ul << c) & mask) != 0 || c == (byte)'|')
return ResultFs.InvalidCharacter.Log();
return Result.Success;
}
public static Result CheckInvalidBackslash(out bool containsBackslash, ReadOnlySpan<byte> path, bool allowBackslash)
{
containsBackslash = false;
for (int i = 0; i < path.Length && path[i] != NullTerminator; i++)
{
if (path[i] == '\\')
{
containsBackslash = true;
if (!allowBackslash)
return ResultFs.InvalidCharacter.Log();
}
}
return Result.Success;
}
public static Result CheckEntryNameBytes(ReadOnlySpan<byte> path, int maxEntryLength)
{
Assert.SdkRequiresNotNull(path);
int currentEntryLength = 0;
for (int i = 0; i < path.Length && path[i] != NullTerminator; i++)
{
currentEntryLength++;
if (path[i] == DirectorySeparator || path[i] == AltDirectorySeparator)
currentEntryLength = 0;
// Note: The original does use >= instead of >
if (currentEntryLength >= maxEntryLength)
return ResultFs.TooLongPath.Log();
}
return Result.Success;
}
public static bool IsSubPath(ReadOnlySpan<byte> lhs, ReadOnlySpan<byte> rhs)
{
Assert.SdkRequiresNotNull(lhs);
Assert.SdkRequiresNotNull(rhs);
if (WindowsPath12.IsUncPath(lhs) && !WindowsPath12.IsUncPath(rhs))
return false;
if (!WindowsPath12.IsUncPath(lhs) && WindowsPath12.IsUncPath(rhs))
return false;
if (lhs.At(0) == DirectorySeparator && lhs.At(1) == NullTerminator &&
rhs.At(0) == DirectorySeparator && rhs.At(1) != NullTerminator)
return true;
if (rhs.At(0) == DirectorySeparator && rhs.At(1) == NullTerminator &&
lhs.At(0) == DirectorySeparator && lhs.At(1) != NullTerminator)
return true;
for (int i = 0; ; i++)
{
if (lhs.At(i) == NullTerminator)
{
return rhs.At(i) == DirectorySeparator;
}
else if (rhs.At(i) == NullTerminator)
{
return lhs.At(i) == DirectorySeparator;
}
else if (lhs.At(i) != rhs.At(i))
{
return false;
}
}
}
public static bool IsPathAbsolute(ReadOnlySpan<byte> path)
{
if (WindowsPath12.IsWindowsPath(path, false))
return true;
return path.At(0) == DirectorySeparator;
}
public static bool IsPathRelative(ReadOnlySpan<byte> path)
{
return path.At(0) != NullTerminator && !IsPathAbsolute(path);
}
public static bool IsPathStartWithCurrentDirectory(ReadOnlySpan<byte> path)
{
return IsCurrentDirectory(path) || IsParentDirectory(path);
}
}
}

View File

@ -1,82 +1,157 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using static LibHac.Fs.StringTraits;
using LibHac.Util;
using static LibHac.Util.CharacterEncoding;
// ReSharper disable once CheckNamespace
namespace LibHac.Fs
{
/// <summary>
/// Contains functions for working with Windows paths.
/// </summary>
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
public static class WindowsPath
{
private const int WindowsDriveLength = 2;
private const int UncPathPrefixLength = 2;
private const int DosDevicePathPrefixLength = 4;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsWindowsDrive(U8Span path)
public static int GetCodePointByteLength(byte firstCodeUnit)
{
Assert.SdkRequires(!path.IsNull());
return (uint)path.Length > 1 &&
IsWindowsDriveCharacter(path[0]) &&
path[1] == DriveSeparator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsWindowsDriveCharacter(byte c)
{
// Mask lowercase letters to uppercase and check if it's in range
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 >= UncPathPrefixLength &&
(path.GetUnsafe(0) == DirectorySeparator && path.GetUnsafe(1) == DirectorySeparator ||
path.GetUnsafe(0) == AltDirectorySeparator && path.GetUnsafe(1) == AltDirectorySeparator);
}
public static bool IsUnc(string path)
{
return (uint)path.Length >= UncPathPrefixLength &&
(path[0] == DirectorySeparator && path[1] == DirectorySeparator ||
path[0] == AltDirectorySeparator && path[1] == AltDirectorySeparator);
}
public static int GetWindowsPathSkipLength(U8Span path)
{
if (IsWindowsDrive(path))
return WindowsDriveLength;
if (!IsUnc(path))
return 0;
for (int i = UncPathPrefixLength; i < path.Length && path[i] != NullTerminator; i++)
{
if (path[i] == (byte)'$' || path[i] == DriveSeparator)
{
return i + 1;
}
}
if ((firstCodeUnit & 0x80) == 0x00) return 1;
if ((firstCodeUnit & 0xE0) == 0xC0) return 2;
if ((firstCodeUnit & 0xF0) == 0xE0) return 3;
if ((firstCodeUnit & 0xF8) == 0xF0) return 4;
return 0;
}
public static bool IsDosDelimiter(char c)
private static bool IsUncPathImpl(ReadOnlySpan<byte> path, bool checkForwardSlash, bool checkBackSlash)
{
return c == '/' || c == '\\';
Assert.SdkRequiresNotNull(path);
if ((uint)path.Length < UncPathPrefixLength)
return false;
if (checkForwardSlash && path[0] == '/' && path[1] == '/')
return true;
return checkBackSlash && path[0] == '\\' && path[1] == '\\';
}
public static bool IsDosDevicePath(ReadOnlySpan<char> path)
private static bool IsUncPathImpl(ReadOnlySpan<char> path, bool checkForwardSlash, bool checkBackSlash)
{
return path.Length >= DosDevicePathPrefixLength
&& IsDosDelimiter(path[0])
&& path[1] == '\\'
&& (path[2] == '.' || path[2] == '?')
&& IsDosDelimiter(path[3]);
Assert.SdkRequiresNotNull(path);
if ((uint)path.Length < UncPathPrefixLength)
return false;
if (checkForwardSlash && path[0] == '/' && path[1] == '/')
return true;
return checkBackSlash && path[0] == '\\' && path[1] == '\\';
}
private static int GetUncPathPrefixLengthImpl(ReadOnlySpan<byte> path, bool checkForwardSlash)
{
Assert.SdkRequiresNotNull(path);
int length;
int separatorCount = 0;
for (length = 0; length < path.Length && path[length] != 0; length++)
{
if (checkForwardSlash && path[length] == '/')
++separatorCount;
if (path[length] == '\\')
++separatorCount;
if (separatorCount == 4)
return length;
}
return length;
}
private static int GetUncPathPrefixLengthImpl(ReadOnlySpan<char> path, bool checkForwardSlash)
{
Assert.SdkRequiresNotNull(path);
int length;
int separatorCount = 0;
for (length = 0; length < path.Length && path[length] != 0; length++)
{
if (checkForwardSlash && path[length] == '/')
++separatorCount;
if (path[length] == '\\')
++separatorCount;
if (separatorCount == 4)
return length;
}
return length;
}
private static bool IsDosDevicePathImpl(ReadOnlySpan<byte> path)
{
Assert.SdkRequiresNotNull(path);
if ((uint)path.Length < DosDevicePathPrefixLength)
return false;
return path[0] == '\\' &&
path[1] == '\\' &&
(path[2] == '.' || path[2] == '?') &&
(path[3] == '/' || path[3] == '\\');
}
private static bool IsDosDevicePathImpl(ReadOnlySpan<char> path)
{
Assert.SdkRequiresNotNull(path);
if ((uint)path.Length < DosDevicePathPrefixLength)
return false;
return path[0] == '\\' &&
path[1] == '\\' &&
(path[2] == '.' || path[2] == '?') &&
(path[3] == '/' || path[3] == '\\');
}
public static bool IsWindowsDrive(ReadOnlySpan<byte> path)
{
Assert.SdkRequiresNotNull(path);
if ((uint)path.Length < WindowsDriveLength)
return false;
// Mask lowercase letters to uppercase and check if it's in range
return ((0b1101_1111 & path[0]) - 'A' <= 'Z' - 'A') && path[1] == ':';
// return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':';
}
public static bool IsUncPath(ReadOnlySpan<byte> path)
{
return IsUncPathImpl(path, true, true);
}
public static bool IsUncPath(ReadOnlySpan<byte> path, bool checkForwardSlash, bool checkBackSlash)
{
return IsUncPathImpl(path, checkForwardSlash, checkBackSlash);
}
public static int GetUncPathPrefixLength(ReadOnlySpan<byte> path)
{
return GetUncPathPrefixLengthImpl(path, true);
}
public static bool IsDosDevicePath(ReadOnlySpan<byte> path)
{
return IsDosDevicePathImpl(path);
}
public static int GetDosDevicePathPrefixLength()
@ -84,37 +159,100 @@ namespace LibHac.Fs
return DosDevicePathPrefixLength;
}
public static bool IsDriveName(ReadOnlySpan<char> path)
public static bool IsWindowsPath(ReadOnlySpan<byte> path, bool checkForwardSlash)
{
return path.Length == WindowsDriveLength && path[1] == ':';
return IsWindowsDrive(path) || IsDosDevicePath(path) || IsUncPath(path, checkForwardSlash, true);
}
public static bool IsUncPath(ReadOnlySpan<char> path)
public static int GetWindowsSkipLength(ReadOnlySpan<byte> path)
{
return !IsDosDevicePath(path) && path.Length >= UncPathPrefixLength && IsDosDelimiter(path[0]) &&
IsDosDelimiter(path[1]);
if (IsDosDevicePath(path))
return GetDosDevicePathPrefixLength();
if (IsWindowsDrive(path))
return WindowsDriveLength;
if (IsUncPath(path))
return GetUncPathPrefixLength(path);
return 0;
}
public static int GetUncPathPrefixLength(ReadOnlySpan<char> path)
public static bool IsDosDelimiterW(char c)
{
int i;
for (i = 0; i < path.Length; i++)
return c == '/' || c == '\\';
}
public static bool IsWindowsDriveW(ReadOnlySpan<char> path)
{
Assert.SdkRequiresNotNull(path);
if ((uint)path.Length < WindowsDriveLength)
return false;
// Mask lowercase letters to uppercase and check if it's in range
return ((0b1101_1111 & path[0]) - 'A' <= 'Z' - 'A') && path[1] == ':';
// return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':';
}
public static bool IsUncPathW(ReadOnlySpan<char> path)
{
return IsUncPathImpl(path, true, true);
}
public static int GetUncPathPrefixLengthW(ReadOnlySpan<char> path)
{
return GetUncPathPrefixLengthImpl(path, true);
}
public static bool IsDosDevicePathW(ReadOnlySpan<char> path)
{
return IsDosDevicePathImpl(path);
}
public static bool IsWindowsPathW(ReadOnlySpan<char> path)
{
return IsWindowsDriveW(path) || IsUncPathW(path) || IsDosDevicePathW(path);
}
public static Result CheckCharacterCountForWindows(ReadOnlySpan<byte> path, int maxNameLength, int maxPathLength)
{
Assert.SdkRequiresNotNull(path);
ReadOnlySpan<byte> currentChar = path;
int currentNameLength = 0;
int currentPathLength = 0;
while (currentChar.Length > 1 && currentChar[0] != 0)
{
if (path[i] == '\0')
break;
int utf16CodeUnitCount = GetCodePointByteLength(currentChar[0]) < 4 ? 1 : 2;
if (IsDosDelimiter(path[i]) && i + 1 == DosDevicePathPrefixLength)
break;
int utf8Buffer = 0;
CharacterEncodingResult result =
PickOutCharacterFromUtf8String(SpanHelpers.AsByteSpan(ref utf8Buffer), ref currentChar);
if (result != CharacterEncodingResult.Success)
return ResultFs.InvalidPathFormat.Log();
result = ConvertCharacterUtf8ToUtf32(out uint pathChar, SpanHelpers.AsReadOnlyByteSpan(in utf8Buffer));
if (result != CharacterEncodingResult.Success)
return ResultFs.InvalidPathFormat.Log();
currentNameLength += utf16CodeUnitCount;
currentPathLength += utf16CodeUnitCount;
if (pathChar == '/' || pathChar == '\\')
currentNameLength = 0;
if (maxNameLength > 0 && currentNameLength > maxNameLength)
return ResultFs.TooLongPath.Log();
if (maxPathLength > 0 && currentPathLength > maxPathLength)
return ResultFs.TooLongPath.Log();
}
return i;
}
public static bool IsPathRooted(ReadOnlySpan<char> path)
{
return IsDriveName(path.Slice(0, Math.Min(WindowsDriveLength, path.Length)))
|| IsDosDevicePath(path.Slice(0, Math.Min(DosDevicePathPrefixLength, path.Length)))
|| IsUncPath(path.Slice(0, Math.Min(DosDevicePathPrefixLength, path.Length)));
return Result.Success;
}
}
}

View File

@ -1,250 +0,0 @@
using System;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Util;
using static LibHac.Util.CharacterEncoding;
// ReSharper disable once CheckNamespace
namespace LibHac.Fs
{
public static class WindowsPath12
{
public static int GetCodePointByteLength(byte firstCodeUnit)
{
if ((firstCodeUnit & 0x80) == 0x00) return 1;
if ((firstCodeUnit & 0xE0) == 0xC0) return 2;
if ((firstCodeUnit & 0xF0) == 0xE0) return 3;
if ((firstCodeUnit & 0xF8) == 0xF0) return 4;
return 0;
}
private static bool IsUncPathImpl(ReadOnlySpan<byte> path, bool checkForwardSlash, bool checkBackSlash)
{
Assert.SdkRequiresNotNull(path);
if ((uint)path.Length < 2)
return false;
if (checkForwardSlash && path[0] == '/' && path[1] == '/')
return true;
return checkBackSlash && path[0] == '\\' && path[1] == '\\';
}
private static bool IsUncPathImpl(ReadOnlySpan<char> path, bool checkForwardSlash, bool checkBackSlash)
{
Assert.SdkRequiresNotNull(path);
if ((uint)path.Length < 2)
return false;
if (checkForwardSlash && path[0] == '/' && path[1] == '/')
return true;
return checkBackSlash && path[0] == '\\' && path[1] == '\\';
}
private static int GetUncPathPrefixLengthImpl(ReadOnlySpan<byte> path, bool checkForwardSlash)
{
Assert.SdkRequiresNotNull(path);
int length;
int separatorCount = 0;
for (length = 0; length < path.Length && path[length] != 0; length++)
{
if (checkForwardSlash && path[length] == '/')
++separatorCount;
if (path[length] == '\\')
++separatorCount;
if (separatorCount == 4)
return length;
}
return length;
}
private static int GetUncPathPrefixLengthImpl(ReadOnlySpan<char> path, bool checkForwardSlash)
{
Assert.SdkRequiresNotNull(path);
int length;
int separatorCount = 0;
for (length = 0; length < path.Length && path[length] != 0; length++)
{
if (checkForwardSlash && path[length] == '/')
++separatorCount;
if (path[length] == '\\')
++separatorCount;
if (separatorCount == 4)
return length;
}
return length;
}
private static bool IsDosDevicePathImpl(ReadOnlySpan<byte> path)
{
Assert.SdkRequiresNotNull(path);
if ((uint)path.Length < 4)
return false;
return path[0] == '\\' &&
path[1] == '\\' &&
(path[2] == '.' || path[2] == '?') &&
(path[3] == '/' || path[3] == '\\');
}
private static bool IsDosDevicePathImpl(ReadOnlySpan<char> path)
{
Assert.SdkRequiresNotNull(path);
if ((uint)path.Length < 4)
return false;
return path[0] == '\\' &&
path[1] == '\\' &&
(path[2] == '.' || path[2] == '?') &&
(path[3] == '/' || path[3] == '\\');
}
public static bool IsWindowsDrive(ReadOnlySpan<byte> path)
{
Assert.SdkRequiresNotNull(path);
if ((uint)path.Length < 2)
return false;
// Mask lowercase letters to uppercase and check if it's in range
return ((0b1101_1111 & path[0]) - 'A' <= 'Z' - 'A') && path[1] == ':';
// return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':';
}
public static bool IsUncPath(ReadOnlySpan<byte> path)
{
return IsUncPathImpl(path, true, true);
}
public static bool IsUncPath(ReadOnlySpan<byte> path, bool checkForwardSlash, bool checkBackSlash)
{
return IsUncPathImpl(path, checkForwardSlash, checkBackSlash);
}
public static int GetUncPathPrefixLength(ReadOnlySpan<byte> path)
{
return GetUncPathPrefixLengthImpl(path, true);
}
public static bool IsDosDevicePath(ReadOnlySpan<byte> path)
{
return IsDosDevicePathImpl(path);
}
public static int GetDosDevicePathPrefixLength()
{
return 4;
}
public static bool IsWindowsPath(ReadOnlySpan<byte> path, bool checkForwardSlash)
{
return IsWindowsDrive(path) || IsDosDevicePath(path) || IsUncPath(path, checkForwardSlash, true);
}
public static int GetWindowsSkipLength(ReadOnlySpan<byte> path)
{
if (IsDosDevicePath(path))
return GetDosDevicePathPrefixLength();
if (IsWindowsDrive(path))
return 2;
if (IsUncPath(path))
return GetUncPathPrefixLength(path);
return 0;
}
public static bool IsDosDelimiterW(char c)
{
return c == '/' || c == '\\';
}
public static bool IsWindowsDriveW(ReadOnlySpan<char> path)
{
Assert.SdkRequiresNotNull(path);
if ((uint)path.Length < 2)
return false;
// Mask lowercase letters to uppercase and check if it's in range
return ((0b1101_1111 & path[0]) - 'A' <= 'Z' - 'A') && path[1] == ':';
// return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':';
}
public static bool IsUncPathW(ReadOnlySpan<char> path)
{
return IsUncPathImpl(path, true, true);
}
public static int GetUncPathPrefixLengthW(ReadOnlySpan<char> path)
{
return GetUncPathPrefixLengthImpl(path, true);
}
public static bool IsDosDevicePathW(ReadOnlySpan<char> path)
{
return IsDosDevicePathImpl(path);
}
public static bool IsWindowsPathW(ReadOnlySpan<char> path)
{
return IsWindowsDriveW(path) || IsUncPathW(path) || IsDosDevicePathW(path);
}
public static Result CheckCharacterCountForWindows(ReadOnlySpan<byte> path, int maxNameLength, int maxPathLength)
{
Assert.SdkRequiresNotNull(path);
ReadOnlySpan<byte> currentChar = path;
int currentNameLength = 0;
int currentPathLength = 0;
while (currentChar.Length > 1 && currentChar[0] != 0)
{
int utf16CodeUnitCount = GetCodePointByteLength(currentChar[0]) < 4 ? 1 : 2;
int utf8Buffer = 0;
CharacterEncodingResult result =
PickOutCharacterFromUtf8String(SpanHelpers.AsByteSpan(ref utf8Buffer), ref currentChar);
if (result != CharacterEncodingResult.Success)
return ResultFs.InvalidPathFormat.Log();
result = ConvertCharacterUtf8ToUtf32(out uint pathChar, SpanHelpers.AsReadOnlyByteSpan(in utf8Buffer));
if (result != CharacterEncodingResult.Success)
return ResultFs.InvalidPathFormat.Log();
currentNameLength += utf16CodeUnitCount;
currentPathLength += utf16CodeUnitCount;
if (pathChar == '/' || pathChar == '\\')
currentNameLength = 0;
if (maxNameLength > 0 && currentNameLength > maxNameLength)
return ResultFs.TooLongPath.Log();
if (maxPathLength > 0 && currentPathLength > maxPathLength)
return ResultFs.TooLongPath.Log();
}
return Result.Success;
}
}
}

View File

@ -26,7 +26,6 @@ namespace LibHac.Fs
public FileSystemProxyServiceObjectGlobals FileSystemProxyServiceObject;
public FsContextHandlerGlobals FsContextHandler;
public ResultHandlingUtilityGlobals ResultHandlingUtility;
public PathUtilityGlobals PathUtility;
public DirectorySaveDataFileSystemGlobals DirectorySaveDataFileSystem;
public void Initialize(FileSystemClient fsClient, HorizonClient horizonClient)
@ -36,7 +35,6 @@ namespace LibHac.Fs
AccessLog.Initialize(fsClient);
UserMountTable.Initialize(fsClient);
FsContextHandler.Initialize(fsClient);
PathUtility.Initialize(fsClient);
DirectorySaveDataFileSystem.Initialize(fsClient);
}
}

View File

@ -20,7 +20,7 @@ namespace LibHac.Fs.Fsa
int mountLen = 0;
int maxMountLen = Math.Min(path.Length, PathTools.MountNameLengthMax);
if (WindowsPath.IsWindowsDrive(path) || WindowsPath.IsUnc(path))
if (WindowsPath.IsWindowsDrive(path) || WindowsPath.IsUncPath(path))
{
StringUtils.Copy(mountName.Name, CommonPaths.HostRootFileSystemMountName);
mountName.Name[PathTools.MountNameLengthMax] = StringTraits.NullTerminator;

View File

@ -157,7 +157,7 @@ namespace LibHac.Fs.Shim
public static Result SetBisRootForHost(this FileSystemClient fs, BisPartitionId partitionId, U8Span rootPath)
{
Unsafe.SkipInit(out FsPath path);
Unsafe.SkipInit(out FsPath pathBuffer);
Result rc;
int pathLen = StringUtils.GetLength(rootPath, PathTools.MaxPathLength + 1);
@ -173,16 +173,19 @@ namespace LibHac.Fs.Shim
? StringTraits.NullTerminator
: StringTraits.DirectorySeparator;
var sb = new U8StringBuilder(path.Str);
rc = sb.Append(rootPath).Append(endingSeparator).ToSfPath();
if (rc.IsFailure()) return rc;
var sb = new U8StringBuilder(pathBuffer.Str);
sb.Append(rootPath).Append(endingSeparator);
if (sb.Overflowed)
return ResultFs.TooLongPath.Log();
}
else
{
path.Str[0] = StringTraits.NullTerminator;
pathBuffer.Str[0] = StringTraits.NullTerminator;
}
FspPath.FromSpan(out FspPath sfPath, path.Str);
rc = PathUtility.ConvertToFspPath(out FspPath sfPath, pathBuffer.Str);
if (rc.IsFailure()) return rc;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();

View File

@ -450,7 +450,7 @@ namespace LibHac.FsSrv.Impl
rc = SetUpPath(ref newPathNormalized, in newPath);
if (rc.IsFailure()) return rc;
if (PathUtility12.IsSubPath(currentPathNormalized.GetString(), newPathNormalized.GetString()))
if (PathUtility.IsSubPath(currentPathNormalized.GetString(), newPathNormalized.GetString()))
return ResultFs.DirectoryNotRenamable.Log();
rc = _baseFileSystem.Target.RenameDirectory(in currentPathNormalized, in newPathNormalized);

View File

@ -168,7 +168,7 @@ namespace LibHac.FsSrv.Impl
rc = resolver.ResolveApplicationHtmlDocumentPath(out Lr.Path path, applicationId);
if (rc.IsFailure()) return rc;
isDirectory = PathUtility12.IsDirectoryPath(path.Str);
isDirectory = PathUtility.IsDirectoryPath(path.Str);
return SetUpFsPath(ref outPath, in path);
}
@ -183,7 +183,7 @@ namespace LibHac.FsSrv.Impl
rc = resolver.ResolveProgramPath(out Lr.Path path, programId);
if (rc.IsFailure()) return rc;
isDirectory = PathUtility12.IsDirectoryPath(path.Str);
isDirectory = PathUtility.IsDirectoryPath(path.Str);
return SetUpFsPath(ref outPath, in path);
}
@ -198,7 +198,7 @@ namespace LibHac.FsSrv.Impl
rc = resolver.ResolveProgramPathForDebug(out Lr.Path path, programId);
if (rc.IsFailure()) return rc;
isDirectory = PathUtility12.IsDirectoryPath(path.Str);
isDirectory = PathUtility.IsDirectoryPath(path.Str);
return SetUpFsPath(ref outPath, in path);
}

View File

@ -1,106 +0,0 @@
using System;
using System.Buffers;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.FsSrv.Impl
{
public ref struct PathNormalizer
{
private readonly U8Span _path;
private byte[] _rentedArray;
public U8Span Path => _path;
public Result Result { get; }
public PathNormalizer(U8Span path, Option option)
{
if (option.HasFlag(Option.AcceptEmpty) && path.IsEmpty())
{
_path = path;
_rentedArray = null;
Result = Result.Success;
}
else
{
bool preserveUnc = option.HasFlag(Option.PreserveUnc);
bool preserveTrailingSeparator = option.HasFlag(Option.PreserveTrailingSeparator);
bool hasMountName = option.HasFlag(Option.HasMountName);
Result = Normalize(out _path, out _rentedArray, path, preserveUnc, preserveTrailingSeparator,
hasMountName);
}
}
public void Dispose()
{
if (_rentedArray is not null)
ArrayPool<byte>.Shared.Return(_rentedArray);
}
private static Result Normalize(out U8Span normalizedPath, out byte[] rentedBuffer, U8Span path,
bool preserveUnc, bool preserveTailSeparator, bool hasMountName)
{
Assert.SdkRequiresNotNullOut(out rentedBuffer);
normalizedPath = default;
rentedBuffer = null;
Result rc = Fs.PathNormalizer.IsNormalized(out bool isNormalized, path, preserveUnc, hasMountName);
if (rc.IsFailure()) return rc;
if (isNormalized)
{
normalizedPath = path;
}
else
{
byte[] buffer = null;
try
{
buffer = ArrayPool<byte>.Shared.Rent(PathTool.EntryNameLengthMax + 1);
rc = Fs.PathNormalizer.Normalize(buffer.AsSpan(0, PathTool.EntryNameLengthMax + 1),
out long normalizedLength, path, preserveUnc, hasMountName);
if (rc.IsFailure()) return rc;
// Add the tail separator if needed
if (preserveTailSeparator)
{
int pathLength = StringUtils.GetLength(path, PathTool.EntryNameLengthMax);
if (Fs.PathNormalizer.IsSeparator(path[pathLength - 1]) &&
!Fs.PathNormalizer.IsSeparator(buffer[normalizedLength - 1]))
{
Assert.SdkLess(normalizedLength, PathTool.EntryNameLengthMax);
buffer[(int)normalizedLength] = StringTraits.DirectorySeparator;
buffer[(int)normalizedLength + 1] = StringTraits.NullTerminator;
}
}
normalizedPath = new U8Span(Shared.Move(ref buffer));
}
finally
{
if (buffer is not null)
ArrayPool<byte>.Shared.Return(buffer);
}
}
return Result.Success;
}
[Flags]
public enum Option
{
None = 0,
PreserveUnc = (1 << 0),
PreserveTrailingSeparator = (1 << 1),
HasMountName = (1 << 2),
AcceptEmpty = (1 << 3)
}
}
}

View File

@ -243,7 +243,7 @@ namespace LibHac.FsSrv
rc = pathNormalized.Normalize(pathFlags);
if (rc.IsFailure()) return rc;
bool isDirectory = PathUtility12.IsDirectoryPath(in path);
bool isDirectory = PathUtility.IsDirectoryPath(in path);
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
@ -375,7 +375,7 @@ namespace LibHac.FsSrv
rc = pathNormalized.Normalize(pathFlags);
if (rc.IsFailure()) return rc;
if (PathUtility12.IsDirectoryPath(in path))
if (PathUtility.IsDirectoryPath(in path))
return ResultFs.TargetNotFound.Log();
rc = ServiceImpl.GetRightsId(out RightsId rightsId, out byte keyGeneration, in pathNormalized,

View File

@ -834,8 +834,8 @@ namespace LibHac.FsSystem
for (int i = 0; i < path.Length; i++)
{
if (!(path[i] == caseSensitivePath[i] || WindowsPath.IsDosDelimiter(path[i]) &&
WindowsPath.IsDosDelimiter(caseSensitivePath[i])))
if (!(path[i] == caseSensitivePath[i] || WindowsPath.IsDosDelimiterW(path[i]) &&
WindowsPath.IsDosDelimiterW(caseSensitivePath[i])))
{
return ResultFs.PathNotFound.Log();
}
@ -853,7 +853,7 @@ namespace LibHac.FsSystem
string fullPath;
int workingDirectoryPathLength;
if (WindowsPath.IsPathRooted(path))
if (WindowsPath.IsWindowsPathW(path))
{
fullPath = path;
workingDirectoryPathLength = 0;
@ -862,7 +862,7 @@ namespace LibHac.FsSystem
{
// We only want to send back the relative part of the path starting with a '/', so
// track where the root path ends.
if (WindowsPath.IsDosDelimiter(workingDirectoryPath[^1]))
if (WindowsPath.IsDosDelimiterW(workingDirectoryPath[^1]))
{
workingDirectoryPathLength = workingDirectoryPath.Length - 1;
}
@ -888,8 +888,8 @@ namespace LibHac.FsSystem
if (string.IsNullOrEmpty(path1)) return path2;
if (string.IsNullOrEmpty(path2)) return path1;
bool path1HasSeparator = WindowsPath.IsDosDelimiter(path1[path1.Length - 1]);
bool path2HasSeparator = WindowsPath.IsDosDelimiter(path2[0]);
bool path1HasSeparator = WindowsPath.IsDosDelimiterW(path1[path1.Length - 1]);
bool path2HasSeparator = WindowsPath.IsDosDelimiterW(path2[0]);
if (!path1HasSeparator && !path2HasSeparator)
{
@ -914,7 +914,7 @@ namespace LibHac.FsSystem
string exactPath = string.Empty;
int itemsToSkip = 0;
if (WindowsPath.IsUnc(path))
if (WindowsPath.IsUncPathW(path))
{
// With the Split method, a UNC path like \\server\share, we need to skip
// trying to enumerate the server and share, so skip the first two empty

View File

@ -33,7 +33,7 @@ namespace LibHac.FsSystem.Save
private Result CheckIfNormalized(in Path path)
{
Result rc = PathNormalizer12.IsNormalized(out bool isNormalized, out _, path.GetString());
Result rc = PathNormalizer.IsNormalized(out bool isNormalized, out _, path.GetString());
if (rc.IsFailure()) return rc;
if (!isNormalized)

View File

@ -52,7 +52,7 @@ namespace LibHac.Tests.Fs
{
byte[] buffer = new byte[0x301];
Result result = PathNormalizer12.Normalize(buffer, out int normalizedLength, path.ToU8Span(), isWindowsPath,
Result result = PathNormalizer.Normalize(buffer, out int normalizedLength, path.ToU8Span(), isWindowsPath,
isDriveRelativePath);
Assert.Equal(expectedResult, result);
@ -86,7 +86,7 @@ namespace LibHac.Tests.Fs
{
byte[] buffer = new byte[bufferLength];
Result result = PathNormalizer12.Normalize(buffer, out int normalizedLength, path.ToU8Span(), false, false);
Result result = PathNormalizer.Normalize(buffer, out int normalizedLength, path.ToU8Span(), false, false);
Assert.Equal(expectedResult, result);
Assert.Equal(expectedNormalized, StringUtils.Utf8ZToString(buffer));
@ -133,7 +133,7 @@ namespace LibHac.Tests.Fs
[Theory, MemberData(nameof(TestData_IsNormalized))]
public static void IsNormalized(string path, bool expectedIsNormalized, long expectedLength, Result expectedResult)
{
Result result = PathNormalizer12.IsNormalized(out bool isNormalized, out int length, path.ToU8Span());
Result result = PathNormalizer.IsNormalized(out bool isNormalized, out int length, path.ToU8Span());
Assert.Equal(expectedResult, result);
Assert.Equal(expectedLength, length);

View File

@ -1,327 +0,0 @@
// 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 < 11
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);
bool IsSubpath(const char* path1, const char* path2);
}
namespace nn::fs {
// SDK >= 11
bool IsSubPath(const char* path1, const char* path2);
}
namespace nn::fs::PathNormalizer {
// SDK >= 11
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 CreateNormalizeTestItem(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::PathNormalizer::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 CreateIsNormalizedTestItem(char const* path, bool preserveUnc, bool hasMountName) {
bool isNormalized = false;
nn::Result result = nn::fs::PathNormalizer::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 CreateIsSubpathTestItem(const char* path1, const char* path2) {
bool result = nn::fs::IsSubPath(path1, path2);
const char* resultStr = result ? "true" : "false";
BufPos += sprintf(&Buf[BufPos], "new object[] {@\"%s\", @\"%s\", %s},\n",
path1, path2, resultStr);
}
void CreateTestItemWithParentDirs(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 CreateTestItemWithParentDirs(char const* path, bool preserveUnc, bool hasMountName, void (*func)(char const*, bool, bool)) {
CreateTestItemWithParentDirs(path, preserveUnc, hasMountName, func, 3);
}
void CreateNormalizationTestData(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, false);
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;
CreateTestItemWithParentDirs("//$abc/bb", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//:abc/bb", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("\\\\\\asd", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("\\\\/asd", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("\\\\//asd", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("\\\\a/b/cc/../d", preserveUnc, false, func);
CreateTestItemWithParentDirs("c:/aa/bb", preserveUnc, true, func);
CreateTestItemWithParentDirs("mount:\\c:/aa", preserveUnc, true, func);
CreateTestItemWithParentDirs("mount:/c:\\aa/bb", preserveUnc, true, func);
CreateTestItemWithParentDirs("mount:////c:\\aa/bb", preserveUnc, true, func);
CreateTestItemWithParentDirs("mount:/\\aa/bb", preserveUnc, true, func);
CreateTestItemWithParentDirs("mount:/c:/aa/bb", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("mount:c:/aa/bb", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("mount:c:/aa/bb", preserveUnc, true, func, 0);
CreateTestItemWithParentDirs("mount:/\\aa/../b", preserveUnc, true, func, 2);
CreateTestItemWithParentDirs("mount://aa/bb", preserveUnc, true, func, 1);
CreateTestItemWithParentDirs("//aa/bb", preserveUnc, true, func, 1);
CreateTestItemWithParentDirs("//aa/bb", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("/aa/bb", preserveUnc, false, func);
CreateTestItemWithParentDirs("c:/aa", preserveUnc, false, func, 2);
CreateTestItemWithParentDirs("c:abcde/aa/bb", preserveUnc, false, func);
CreateTestItemWithParentDirs("c:abcde", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("c:abcde/", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("///aa", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//aa//bb", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("//./bb", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//../bb", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//.../bb", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//aa$abc/bb", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//aa$/bb", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//aa:/bb", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//aa/bb$b/cc$", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//aa/bb/cc$c", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("//aa/bb/cc$c/dd", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//aa/bb", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//aa/bb/cc//dd", preserveUnc, false, func);
CreateTestItemWithParentDirs("//aa/bb/cc\\/dd", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//aa/bb/cc//dd", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//aa/bb/cc/dd", preserveUnc, false, func);
CreateTestItemWithParentDirs("//aa/bb/cc/\\dd", preserveUnc, false, func);
CreateTestItemWithParentDirs("//aa/../", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//aa//", preserveUnc, false, func, 0);
CreateTestItemWithParentDirs("//aa/bb..", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("//aa/bb../", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("/\\\\aa/bb/cc/..", preserveUnc, true, func);
CreateTestItemWithParentDirs("c:aa\\bb/cc", preserveUnc, false, func);
CreateTestItemWithParentDirs("c:\\//\\aa\\bb", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("mount://////a/bb/c", preserveUnc, true, func, 2);
CreateTestItemWithParentDirs("//", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("//a", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("//a", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("//a/", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("//a/b", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("//a/b/", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("//a/b/c", preserveUnc, false, func, 2);
CreateTestItemWithParentDirs("//a/b/c/", preserveUnc, false, func, 2);
CreateTestItemWithParentDirs("\\\\", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("\\\\a", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("\\\\a/", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("\\\\a/b", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("\\\\a/b/", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("\\\\a/b/c", preserveUnc, false, func, 2);
CreateTestItemWithParentDirs("\\\\a/b/c/", preserveUnc, false, func, 2);
CreateTestItemWithParentDirs("\\\\", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("\\\\a", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("\\\\a\\", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("\\\\a\\b", preserveUnc, false, func, 1);
CreateTestItemWithParentDirs("\\\\a\\b\\", preserveUnc, false, func, 1); // "\\a\b\/../.." crashes nnsdk
CreateTestItemWithParentDirs("\\\\a\\b\\c", preserveUnc, false, func, 2);
CreateTestItemWithParentDirs("\\\\a\\b\\c\\", preserveUnc, false, func, 2);
}
svcOutputDebugString(Buf, BufPos);
}
void CreateSubpathTestData() {
Buf[0] = '\n';
BufPos = 1;
CreateIsSubpathTestItem("//a/b", "/a");
CreateIsSubpathTestItem("/a", "//a/b");
CreateIsSubpathTestItem("//a/b", "\\\\a");
CreateIsSubpathTestItem("//a/b", "//a");
CreateIsSubpathTestItem("/", "/a");
CreateIsSubpathTestItem("/a", "/");
CreateIsSubpathTestItem("/", "/");
CreateIsSubpathTestItem("", "");
CreateIsSubpathTestItem("/", "");
CreateIsSubpathTestItem("/", "mount:/a");
CreateIsSubpathTestItem("mount:/", "mount:/");
CreateIsSubpathTestItem("mount:/a/b", "mount:/a/b");
CreateIsSubpathTestItem("mount:/a/b", "mount:/a/b/c");
CreateIsSubpathTestItem("/a/b", "/a/b/c");
CreateIsSubpathTestItem("/a/b/c", "/a/b");
CreateIsSubpathTestItem("/a/b", "/a/b");
CreateIsSubpathTestItem("/a/b", "/a/b\\c");
svcOutputDebugString(Buf, BufPos);
}
extern "C" void nnMain(void) {
//setAllocators();
nn::fs::detail::IsEnabledAccessLog(); // Adds the sdk version to the output when not calling setAllocators
CreateNormalizationTestData(CreateNormalizeTestItem);
CreateNormalizationTestData(CreateIsNormalizedTestItem);
CreateSubpathTestData();
}

View File

@ -1,850 +0,0 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Util;
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)
{
byte[] buffer = new byte[0x301];
Result result = PathNormalizer.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 = PathNormalizer.IsNormalized(out bool isNormalized, path.ToU8Span(), preserveUnc, hasMountName);
Assert.Equal(expectedResult, result);
if (result.IsSuccess())
{
Assert.Equal(expectedIsNormalized, isNormalized);
}
}
[Theory]
[MemberData(nameof(SubpathTestItems))]
public static void IsSubPath(string path1, string path2, bool expectedResult)
{
bool result = PathUtility.IsSubPath(path1.ToU8Span(), path2.ToU8Span());
Assert.Equal(expectedResult, result);
}
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[] {@"mount:/a/b/../c", false, false, @"", 0, ResultFs.InvalidPathFormat.Value},
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[] {@"mount:/a/b/../c", false, false, false, ResultFs.InvalidPathFormat.Value},
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}
};
public static object[][] SubpathTestItems =
{
new object[] {@"//a/b", @"/a", false},
new object[] {@"/a", @"//a/b", false},
new object[] {@"//a/b", @"\\a", false},
new object[] {@"//a/b", @"//a", true},
new object[] {@"/", @"/a", true},
new object[] {@"/a", @"/", true},
new object[] {@"/", @"/", false},
new object[] {@"", @"", false},
new object[] {@"/", @"", true},
new object[] {@"/", @"mount:/a", false},
new object[] {@"mount:/", @"mount:/", false},
new object[] {@"mount:/a/b", @"mount:/a/b", false},
new object[] {@"mount:/a/b", @"mount:/a/b/c", true},
new object[] {@"/a/b", @"/a/b/c", true},
new object[] {@"/a/b/c", @"/a/b", true},
new object[] {@"/a/b", @"/a/b", false},
new object[] {@"/a/b", @"/a/b\c", false}
};
}
}

View File

@ -32,7 +32,7 @@ namespace LibHac.Tests.Fs
[Theory, MemberData(nameof(TestData_IsSubPath))]
public static void IsSubPath(string path1, string path2, bool expectedResult)
{
bool result = PathUtility12.IsSubPath(path1.ToU8Span(), path2.ToU8Span());
bool result = PathUtility.IsSubPath(path1.ToU8Span(), path2.ToU8Span());
Assert.Equal(expectedResult, result);
}

View File

@ -1,84 +0,0 @@
using LibHac.Common;
using LibHac.Fs;
using Xunit;
using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer;
namespace LibHac.Tests.FsSrv
{
public class PathNormalizerTests
{
[Fact]
public static void Ctor_EmptyPathWithAcceptEmptyOption_ReturnsEmptyPathWithSuccess()
{
using var normalizer = new PathNormalizer("".ToU8Span(), PathNormalizer.Option.AcceptEmpty);
Assert.Equal(Result.Success, normalizer.Result);
Assert.True(normalizer.Path.IsEmpty());
}
[Fact]
public static void Normalize_PreserveTailSeparatorOption_KeepsExistingTailSeparator()
{
using var normalizer = new PathNormalizer("/a/./b/".ToU8Span(), PathNormalizer.Option.PreserveTrailingSeparator);
Assert.Equal(Result.Success, normalizer.Result);
Assert.Equal("/a/b/", normalizer.Path.ToString());
}
[Fact]
public static void Normalize_PreserveTailSeparatorOption_IgnoresMissingTailSeparator()
{
using var normalizer = new PathNormalizer("/a/./b".ToU8Span(), PathNormalizer.Option.PreserveTrailingSeparator);
Assert.Equal(Result.Success, normalizer.Result);
Assert.Equal("/a/b", normalizer.Path.ToString());
}
[Fact]
public static void Normalize_PathAlreadyNormalized_ReturnsSameBuffer()
{
var originalPath = "/a/b".ToU8Span();
using var normalizer = new PathNormalizer(originalPath, PathNormalizer.Option.PreserveTrailingSeparator);
Assert.Equal(Result.Success, normalizer.Result);
// Compares addresses and lengths of the buffers
Assert.True(originalPath.Value == normalizer.Path.Value);
}
[Fact]
public static void Normalize_PreserveUncOptionOn_PreservesUncPath()
{
using var normalizer = new PathNormalizer("//aa/bb/..".ToU8Span(), PathNormalizer.Option.PreserveUnc);
Assert.Equal(Result.Success, normalizer.Result);
Assert.Equal(@"\\aa/bb", normalizer.Path.ToString());
}
[Fact]
public static void Normalize_PreserveUncOptionOff_DoesNotPreserveUncPath()
{
using var normalizer = new PathNormalizer("//aa/bb/..".ToU8Span(), PathNormalizer.Option.None);
Assert.Equal(Result.Success, normalizer.Result);
Assert.Equal(@"/aa", normalizer.Path.ToString());
}
[Fact]
public static void Normalize_MountNameOptionOn_ParsesMountName()
{
using var normalizer = new PathNormalizer("mount:/a/./b".ToU8Span(), PathNormalizer.Option.HasMountName);
Assert.Equal(Result.Success, normalizer.Result);
Assert.Equal("mount:/a/b", normalizer.Path.ToString());
}
[Fact]
public static void Normalize_MountNameOptionOff_DoesNotParseMountName()
{
using var normalizer = new PathNormalizer("mount:/a/./b".ToU8Span(), PathNormalizer.Option.None);
Assert.Equal(ResultFs.InvalidPathFormat.Value, normalizer.Result);
}
}
}