diff --git a/src/LibHac/Fs/Common/DirectoryPathParser.cs b/src/LibHac/Fs/Common/DirectoryPathParser.cs index d0c0bc0f..49f7cfa0 100644 --- a/src/LibHac/Fs/Common/DirectoryPathParser.cs +++ b/src/LibHac/Fs/Common/DirectoryPathParser.cs @@ -23,7 +23,7 @@ namespace LibHac.Fs.Common { Span pathBuffer = path.GetWriteBufferLength() != 0 ? path.GetWriteBuffer() : Span.Empty; - int windowsSkipLength = WindowsPath12.GetWindowsSkipLength(pathBuffer); + int windowsSkipLength = WindowsPath.GetWindowsSkipLength(pathBuffer); _buffer = pathBuffer.Slice(windowsSkipLength); if (windowsSkipLength != 0) diff --git a/src/LibHac/Fs/Common/Path.cs b/src/LibHac/Fs/Common/Path.cs index 90d66074..b00ba58f 100644 --- a/src/LibHac/Fs/Common/Path.cs +++ b/src/LibHac/Fs/Common/Path.cs @@ -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 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) diff --git a/src/LibHac/Fs/Common/PathFormatter.cs b/src/LibHac/Fs/Common/PathFormatter.cs index 4079c15c..42899eaf 100644 --- a/src/LibHac/Fs/Common/PathFormatter.cs +++ b/src/LibHac/Fs/Common/PathFormatter.cs @@ -11,6 +11,10 @@ using static LibHac.Fs.StringTraits; // ReSharper disable once CheckNamespace namespace LibHac.Fs { + /// + /// Contains functions for working with path formatting and normalization. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) 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 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.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; diff --git a/src/LibHac/Fs/Common/PathNormalizer.cs b/src/LibHac/Fs/Common/PathNormalizer.cs index f0ab0d96..6b42698a 100644 --- a/src/LibHac/Fs/Common/PathNormalizer.cs +++ b/src/LibHac/Fs/Common/PathNormalizer.cs @@ -1,380 +1,321 @@ using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Diag; using LibHac.FsSystem; +using static LibHac.Fs.PathUtility; using static LibHac.Fs.StringTraits; // ReSharper disable once CheckNamespace namespace LibHac.Fs { - // Previous normalization code can be found in commit 1acdd86e27de16703fdb1c77f50ed8fd71bd3ad7 + /// + /// Contains functions for doing with basic path normalization. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) public static class PathNormalizer { - private enum NormalizeState + private enum PathState { Initial, Normal, FirstSeparator, Separator, - Dot, - DoubleDot + CurrentDir, + ParentDir } - /// - /// Checks if a host-name or share-name in a UNC path are "." or ".." - /// - /// Up to the first two characters in a segment of a UNC path. - /// : The operation was successful.
- /// : A buffer could not be allocated.
- private static Result CheckSharedName(U8Span path) + public static Result Normalize(Span outputBuffer, out int length, ReadOnlySpan path, bool isWindowsPath, + bool isDriveRelativePath) { - if (path.Length == 1 && path.GetUnsafe(0) == Dot) - return ResultFs.InvalidPathFormat.Log(); + UnsafeHelpers.SkipParamInit(out length); - if (path.Length == 2 && path.GetUnsafe(0) == Dot && path.GetUnsafe(1) == Dot) - return ResultFs.InvalidPathFormat.Log(); + ReadOnlySpan currentPath = path; + int totalLength = 0; + int i = 0; - return Result.Success; - } - - private static Result ParseWindowsPath(out U8Span newPath, Span buffer, out long windowsPathLength, - out bool isUncNormalized, U8Span path, bool hasMountName) - { - UnsafeHelpers.SkipParamInit(out windowsPathLength, out isUncNormalized); - newPath = default; - - U8Span currentPath = path; - - if (!Unsafe.IsNullRef(ref isUncNormalized)) - isUncNormalized = true; - - bool skippedMount = false; - int prefixLength = 0; - - if (hasMountName) + if (!IsSeparator(path.At(0))) { - if (path.GetOrNull(0) == DirectorySeparator && path.GetOrNull(1) == AltDirectorySeparator && - path.GetOrNull(1) == AltDirectorySeparator) - { - currentPath = currentPath.Slice(1); - skippedMount = true; - } - else - { - int separatorCount = 0; + if (!isDriveRelativePath) + return ResultFs.InvalidPathFormat.Log(); - while (IsSeparator(currentPath.GetOrNull(separatorCount))) + outputBuffer[totalLength++] = DirectorySeparator; + } + + var convertedPath = new RentedArray(); + try + { + // Check if parent directory path replacement is needed. + if (IsParentDirectoryPathReplacementNeeded(currentPath)) + { + // Allocate a buffer to hold the replacement path. + convertedPath = new RentedArray(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])) { - separatorCount++; + 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; } - if (separatorCount != 0) + int dirLen = 0; + while (!IsSeparator(currentPath.At(i + dirLen)) && !IsNul(currentPath.At(i + dirLen))) { - if (separatorCount == 1 || WindowsPath.IsWindowsDrive(currentPath.Slice(separatorCount))) + 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) { - currentPath = currentPath.Slice(separatorCount); - skippedMount = true; + if (!isWindowsPath) + return ResultFs.DirectoryUnobtainable.Log(); + + totalLength--; } else { - if (separatorCount > 2 && !Unsafe.IsNullRef(ref isUncNormalized)) + totalLength -= 2; + + do { - isUncNormalized = false; - return Result.Success; - } - - currentPath = currentPath.Slice(separatorCount - 2); - prefixLength = 1; - } - } - } - } - else if (path.GetOrNull(0) == DirectorySeparator && !WindowsPath.IsUnc(path)) - { - currentPath = currentPath.Slice(1); - skippedMount = true; - } - - U8Span trimmedPath = path; - - if (WindowsPath.IsWindowsDrive(currentPath)) - { - int i; - for (i = 2; currentPath.GetOrNull(i) != NullTerminator; i++) - { - if (currentPath[i] == DirectorySeparator || currentPath[i] == AltDirectorySeparator) - { - trimmedPath = currentPath.Slice(i); - break; - } - } - - if (trimmedPath.Value == path.Value) - trimmedPath = currentPath.Slice(i); - - ref byte pathStart = ref MemoryMarshal.GetReference(path.Value); - ref byte trimmedPathStart = ref MemoryMarshal.GetReference(trimmedPath.Value); - int winPathLength = (int)Unsafe.ByteOffset(ref pathStart, ref trimmedPathStart); - - if (!buffer.IsEmpty) - { - if (winPathLength > buffer.Length) - return ResultFs.TooLongPath.Log(); - - path.Value.Slice(0, winPathLength).CopyTo(buffer); - } - - newPath = trimmedPath; - windowsPathLength = winPathLength; - return Result.Success; - } - // A UNC path should be in the format "\\" host-name "\" share-name [ "\" object-name ] - else if (WindowsPath.IsUnc(currentPath)) - { - if (currentPath.GetOrNull(2) == DirectorySeparator || currentPath.GetOrNull(2) == AltDirectorySeparator) - { - Assert.SdkAssert(!hasMountName); - return ResultFs.InvalidPathFormat.Log(); - } - else - { - bool needsSeparatorFix = false; - int currentComponentOffset = 0; - - for (int i = 2; currentPath.GetOrNull(i) != NullTerminator; i++) - { - byte c = currentPath.GetUnsafe(i); - - // Check if we need to fix the separators - if (currentComponentOffset == 0 && c == AltDirectorySeparator) - { - needsSeparatorFix = true; - - if (!Unsafe.IsNullRef(ref isUncNormalized)) - { - isUncNormalized = false; - return Result.Success; - } - } - - if (c == DirectorySeparator || c == AltDirectorySeparator) - { - if (c == AltDirectorySeparator) - needsSeparatorFix = true; - - if (currentComponentOffset != 0) - break; - - if (IsSeparator(currentPath.GetOrNull(i + 1))) - return ResultFs.InvalidPathFormat.Log(); - - Result rc = CheckSharedName(currentPath.Slice(2, i - 2)); - if (rc.IsFailure()) return rc; - - currentComponentOffset = i + 1; - } - - if (c == (byte)'$' || c == DriveSeparator) - { - if (currentComponentOffset == 0) - return ResultFs.InvalidCharacter.Log(); - - // A '$' or ':' must be the last character in share-name - byte nextChar = currentPath.GetOrNull(i + 1); - if (nextChar != DirectorySeparator && nextChar != AltDirectorySeparator && - nextChar != NullTerminator) - { - return ResultFs.InvalidPathFormat.Log(); - } - - trimmedPath = currentPath.Slice(i + 1); - break; - } - } - - if (trimmedPath.Value == path.Value) - { - int trimmedPartOffset = 0; - - int i; - for (i = 2; currentPath.GetOrNull(i) != NullTerminator; i++) - { - byte c = currentPath.GetUnsafe(i); - - if (c == DirectorySeparator || c == AltDirectorySeparator) - { - Result rc; - - if (trimmedPartOffset != 0) - { - rc = CheckSharedName(currentPath.Slice(trimmedPartOffset, i - trimmedPartOffset)); - if (rc.IsFailure()) return rc; - - trimmedPath = currentPath.Slice(i); + if (outputBuffer[totalLength] == DirectorySeparator) break; - } - if (IsSeparator(currentPath.GetOrNull(i + 1))) - { - return ResultFs.InvalidPathFormat.Log(); - } - - rc = CheckSharedName(currentPath.Slice(2, i - 2)); - if (rc.IsFailure()) return rc; - - trimmedPartOffset = i + 1; - } + totalLength--; + } while (totalLength != 0); } - if (trimmedPartOffset != 0 && trimmedPath.Value == path.Value) - { - Result rc = CheckSharedName(currentPath.Slice(trimmedPartOffset, i - trimmedPartOffset)); - if (rc.IsFailure()) return rc; + if (!isWindowsPath) + Assert.SdkAssert(outputBuffer[totalLength] == DirectorySeparator); - trimmedPath = currentPath.Slice(i); - } + Assert.SdkAssert(totalLength < outputBuffer.Length); } - - ref byte trimmedPathStart = ref MemoryMarshal.GetReference(trimmedPath.Value); - ref byte currentPathStart = ref MemoryMarshal.GetReference(currentPath.Value); - int mountLength = (int)Unsafe.ByteOffset(ref currentPathStart, ref trimmedPathStart); - bool prependSeparator = prefixLength != 0 || skippedMount; - - if (!buffer.IsEmpty) + else { - if (mountLength > buffer.Length) + 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(); } - Span currentBuffer = buffer; - if (prependSeparator) + for (int j = 0; j < dirLen; j++) { - currentBuffer[0] = DirectorySeparator; - currentBuffer = currentBuffer.Slice(1); - } - - currentPath.Value.Slice(0, mountLength).CopyTo(currentBuffer); - currentBuffer[0] = AltDirectorySeparator; - currentBuffer[1] = AltDirectorySeparator; - - if (needsSeparatorFix) - { - for (int i = 2; i < mountLength; i++) - { - if (currentBuffer[i] == AltDirectorySeparator) - currentBuffer[i] = DirectorySeparator; - } + outputBuffer[totalLength++] = currentPath[i + j]; } } - newPath = trimmedPath; - windowsPathLength = mountLength + (prependSeparator ? 1 : 0); - return Result.Success; + i += dirLen; } - } - else - { - newPath = trimmedPath; - return Result.Success; - } - } - private static Result SkipWindowsPath(out U8Span newPath, out bool isUncNormalized, U8Span path, - bool hasMountName) - { - return ParseWindowsPath(out newPath, Span.Empty, out _, out isUncNormalized, path, hasMountName); - } + if (skipNextSeparator) + totalLength--; - private static Result ParseMountName(out U8Span newPath, Span outMountNameBuffer, - out long mountNameLength, U8Span path) - { - UnsafeHelpers.SkipParamInit(out mountNameLength); - newPath = default; - - int mountStart = IsSeparator(path.GetOrNull(0)) ? 1 : 0; - int mountEnd; - - int maxMountLength = Math.Min(PathTools.MountNameLengthMax, path.Length - mountStart); - - for (mountEnd = mountStart; mountEnd <= maxMountLength; mountEnd++) - { - byte c = path[mountEnd]; - - if (IsSeparator(c)) + if (totalLength == 0 && outputBuffer.Length != 0) { - newPath = path; - mountNameLength = 0; - - return Result.Success; + totalLength = 1; + outputBuffer[0] = DirectorySeparator; } - if (c == DriveSeparator) - { - mountEnd++; - break; - } - - if (c == NullTerminator) - { - break; - } - } - - if (mountStart >= mountEnd - 1 || path[mountEnd - 1] != DriveSeparator) - return ResultFs.InvalidPathFormat.Log(); - - if (mountEnd != mountStart) - { - for (int i = mountStart; i < mountEnd; i++) - { - if (path[i] == Dot) - return ResultFs.InvalidCharacter.Log(); - } - } - - if (!outMountNameBuffer.IsEmpty) - { - if (mountEnd - mountStart > outMountNameBuffer.Length) + // Note: This bug is in the original code. They probably meant to put "totalLength + 1" + if (totalLength - 1 > outputBuffer.Length) return ResultFs.TooLongPath.Log(); - path.Value.Slice(0, mountEnd).CopyTo(outMountNameBuffer); + 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(); + } + } + + /// + /// 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. + /// + /// When this function returns , + /// contains if the path is normalized or if it is not. + /// Contents are undefined if the function does not return . + /// + /// When this function returns and + /// is , contains the length of the normalized path. + /// Contents are undefined if the function does not return + /// or is . + /// + /// The path to check. + /// : The operation was successful.
+ /// : The path contains an invalid character.
+ /// : The path is not in a valid format.
+ public static Result IsNormalized(out bool isNormalized, out int length, ReadOnlySpan 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; + } } - newPath = path.Slice(mountEnd); - mountNameLength = mountEnd - mountStart; + 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; } - private static Result SkipMountName(out U8Span newPath, U8Span path) - { - return ParseMountName(out newPath, Span.Empty, out _, path); - } /// /// Checks if a path begins with / or \ and contains any of these patterns: /// "/..\", "\..\", "\../", "\..0" where '0' is the null terminator. /// - private static bool IsParentDirectoryPathReplacementNeeded(U8Span path) + public static bool IsParentDirectoryPathReplacementNeeded(ReadOnlySpan path) { - if (!IsAnySeparator(path.GetOrNull(0))) + 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.GetOrNull(i + 3); + byte c3 = path.At(i + 3); if (path[i] == AltDirectorySeparator && path[i + 1] == Dot && path[i + 2] == Dot && - (IsAnySeparator(c3) || c3 == NullTerminator)) + (c3 == DirectorySeparator || c3 == AltDirectorySeparator || c3 == NullTerminator)) { return true; } - if (IsAnySeparator(path[i]) && + if ((path[i] == DirectorySeparator || path[i] == AltDirectorySeparator) && path[i + 1] == Dot && path[i + 2] == Dot && c3 == AltDirectorySeparator) @@ -394,10 +335,10 @@ namespace LibHac.Fs while (source.Length > i && source[i] != NullTerminator) { if (source.Length > i + 2 && - IsAnySeparator(source[i - 1]) && - source[i + 0] == Dot && - source[i + 1] == Dot && - IsAnySeparator(source[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; @@ -427,370 +368,5 @@ namespace LibHac.Fs dest[i] = NullTerminator; } - - public static Result Normalize(Span outputBuffer, out long normalizedLength, U8Span path, - bool preserveUnc, bool hasMountName) - { - UnsafeHelpers.SkipParamInit(out normalizedLength); - - U8Span currentPath = path; - int prefixLength = 0; - bool isUncPath = false; - - if (hasMountName) - { - Result rc = ParseMountName(out currentPath, outputBuffer, out long mountNameLength, currentPath); - if (rc.IsFailure()) return rc; - - prefixLength += (int)mountNameLength; - } - - if (preserveUnc) - { - U8Span originalPath = currentPath; - - Result rc = ParseWindowsPath(out currentPath, outputBuffer.Slice(prefixLength), - out long windowsPathLength, out Unsafe.NullRef(), currentPath, hasMountName); - if (rc.IsFailure()) return rc; - - prefixLength += (int)windowsPathLength; - if (originalPath.Value != currentPath.Value) - { - isUncPath = true; - } - } - - // Paths must start with / - if (prefixLength == 0 && !IsSeparator(currentPath.GetOrNull(0))) - return ResultFs.InvalidPathFormat.Log(); - - var convertedPath = new RentedArray(); - try - { - // Check if parent directory path replacement is needed. - if (IsParentDirectoryPathReplacementNeeded(currentPath)) - { - // Allocate a buffer to hold the replacement path. - convertedPath = new RentedArray(PathTools.MaxPathLength + 1); - - // Replace the path. - ReplaceParentDirectoryPath(convertedPath.Span, currentPath); - - // Set current path to be the replacement path. - currentPath = new U8Span(convertedPath.Span); - } - - bool skipNextSep = false; - int i = 0; - int totalLength = prefixLength; - - while (!IsNul(currentPath.GetOrNull(i))) - { - if (IsSeparator(currentPath[i])) - { - do - { - i++; - } while (IsSeparator(currentPath.GetOrNull(i))); - - if (IsNul(currentPath.GetOrNull(i))) - break; - - if (!skipNextSep) - { - if (totalLength + 1 == outputBuffer.Length) - { - outputBuffer[totalLength] = NullTerminator; - normalizedLength = totalLength; - - return ResultFs.TooLongPath.Log(); - } - - outputBuffer[totalLength++] = DirectorySeparator; - } - - skipNextSep = false; - } - - int dirLen = 0; - while (!IsSeparator(currentPath.GetOrNull(i + dirLen)) && !IsNul(currentPath.GetOrNull(i + dirLen))) - { - dirLen++; - } - - if (IsCurrentDirectory(currentPath.Slice(i))) - { - skipNextSep = true; - } - else if (IsParentDirectory(currentPath.Slice(i))) - { - Assert.SdkAssert(outputBuffer[totalLength - 1] == DirectorySeparator); - Assert.SdkAssert(outputBuffer[prefixLength] == DirectorySeparator); - - if (totalLength == prefixLength + 1) - { - if (!isUncPath) - return ResultFs.DirectoryUnobtainable.Log(); - - totalLength--; - } - else - { - totalLength -= 2; - - do - { - if (outputBuffer[totalLength] == DirectorySeparator) - break; - - totalLength--; - } while (totalLength != prefixLength); - } - - if (!isUncPath) - 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; - normalizedLength = totalLength; - return ResultFs.TooLongPath.Log(); - } - else - { - for (int j = 0; j < dirLen; j++) - { - outputBuffer[totalLength++] = currentPath[i + j]; - } - } - } - - i += dirLen; - } - - if (skipNextSep) - totalLength--; - - if (!isUncPath && totalLength == prefixLength && totalLength < outputBuffer.Length) - { - outputBuffer[prefixLength] = DirectorySeparator; - totalLength++; - } - - if (totalLength - 1 > outputBuffer.Length) - { - return ResultFs.TooLongPath.Log(); - } - else - { - outputBuffer[totalLength] = NullTerminator; - normalizedLength = totalLength; - - Assert.SdkAssert(IsNormalized(out bool normalized, new U8Span(outputBuffer), preserveUnc, hasMountName).IsSuccess()); - Assert.SdkAssert(normalized); - } - - return Result.Success; - } - finally - { - convertedPath.Dispose(); - } - } - - public static Result IsNormalized(out bool isNormalized, U8Span path, bool preserveUnc, bool hasMountName) - { - UnsafeHelpers.SkipParamInit(out isNormalized); - - U8Span currentPath = path; - U8Span originalPath = path; - bool isUncPath = false; - - if (hasMountName) - { - Result rc = SkipMountName(out currentPath, originalPath); - if (rc.IsFailure()) return rc; - - if (currentPath.GetOrNull(0) != DirectorySeparator) - return ResultFs.InvalidPathFormat.Log(); - } - - if (preserveUnc) - { - originalPath = currentPath; - - Result rc = SkipWindowsPath(out currentPath, out bool isUncNormalized, currentPath, hasMountName); - if (rc.IsFailure()) return rc; - - if (!isUncNormalized) - { - isNormalized = false; - return Result.Success; - } - - // Path is a UNC path if the new path skips part of the original - isUncPath = originalPath.Value != currentPath.Value; - - if (isUncPath) - { - if (IsSeparator(originalPath.GetOrNull(0)) && IsSeparator(originalPath.GetOrNull(1))) - { - isNormalized = false; - return Result.Success; - } - - if (IsNul(currentPath.GetOrNull(0))) - { - isNormalized = true; - return Result.Success; - } - } - } - - if (IsParentDirectoryPathReplacementNeeded(currentPath)) - { - isNormalized = false; - return Result.Success; - } - - var state = NormalizeState.Initial; - - for (int i = 0; i < currentPath.Length; i++) - { - byte c = currentPath[i]; - if (c == NullTerminator) break; - - switch (state) - { - case NormalizeState.Initial: - if (c == DirectorySeparator) - { - state = NormalizeState.FirstSeparator; - } - else - { - if (currentPath.Value == originalPath.Value) - return ResultFs.InvalidPathFormat.Log(); - - state = c == Dot ? NormalizeState.Dot : NormalizeState.Normal; - } - - break; - case NormalizeState.Normal: - if (c == DirectorySeparator) - { - state = NormalizeState.Separator; - } - - break; - case NormalizeState.FirstSeparator: - case NormalizeState.Separator: - if (c == DirectorySeparator) - { - isNormalized = false; - return Result.Success; - } - - state = c == Dot ? NormalizeState.Dot : NormalizeState.Normal; - break; - case NormalizeState.Dot: - if (c == DirectorySeparator) - { - isNormalized = false; - return Result.Success; - } - - state = c == Dot ? NormalizeState.DoubleDot : NormalizeState.Normal; - break; - case NormalizeState.DoubleDot: - if (c == DirectorySeparator) - { - isNormalized = false; - return Result.Success; - } - - state = NormalizeState.Normal; - break; - // ReSharper disable once UnreachableSwitchCaseDueToIntegerAnalysis - default: - Abort.UnexpectedDefault(); - break; - } - } - - switch (state) - { - case NormalizeState.Initial: - return ResultFs.InvalidPathFormat.Log(); - case NormalizeState.Normal: - isNormalized = true; - break; - case NormalizeState.FirstSeparator: - isNormalized = !isUncPath; - break; - case NormalizeState.Separator: - case NormalizeState.Dot: - case NormalizeState.DoubleDot: - isNormalized = false; - break; - // ReSharper disable once UnreachableSwitchCaseDueToIntegerAnalysis - default: - Abort.UnexpectedDefault(); - break; - } - - return Result.Success; - } - - public static bool IsCurrentDirectory(ReadOnlySpan p) - { - if (p.Length < 1) - return false; - - ref byte b = ref MemoryMarshal.GetReference(p); - - return b == Dot && - (p.Length == 1 || Unsafe.Add(ref b, 1) == NullTerminator || - Unsafe.Add(ref b, 1) == DirectorySeparator); - } - - public static bool IsParentDirectory(ReadOnlySpan p) - { - if (p.Length < 2) - return false; - - ref byte b = ref MemoryMarshal.GetReference(p); - - return b == Dot && - Unsafe.Add(ref b, 1) == Dot && - (p.Length == 2 || Unsafe.Add(ref b, 2) == NullTerminator || - Unsafe.Add(ref b, 2) == DirectorySeparator); - } - - public static bool IsNul(byte c) - { - return c == NullTerminator; - } - - public static bool IsSeparator(byte c) - { - return c == DirectorySeparator; - } - - public static bool IsAnySeparator(byte c) - { - return c == DirectorySeparator || c == AltDirectorySeparator; - } } } diff --git a/src/LibHac/Fs/Common/PathNormalizer12.cs b/src/LibHac/Fs/Common/PathNormalizer12.cs deleted file mode 100644 index 298f2c27..00000000 --- a/src/LibHac/Fs/Common/PathNormalizer12.cs +++ /dev/null @@ -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 outputBuffer, out int length, ReadOnlySpan path, bool isWindowsPath, - bool isDriveRelativePath) - { - UnsafeHelpers.SkipParamInit(out length); - - ReadOnlySpan 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(); - try - { - // Check if parent directory path replacement is needed. - if (IsParentDirectoryPathReplacementNeeded(currentPath)) - { - // Allocate a buffer to hold the replacement path. - convertedPath = new RentedArray(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(); - } - } - - /// - /// 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. - /// - /// When this function returns , - /// contains if the path is normalized or if it is not. - /// Contents are undefined if the function does not return . - /// - /// When this function returns and - /// is , contains the length of the normalized path. - /// Contents are undefined if the function does not return - /// or is . - /// - /// The path to check. - /// : The operation was successful.
- /// : The path contains an invalid character.
- /// : The path is not in a valid format.
- public static Result IsNormalized(out bool isNormalized, out int length, ReadOnlySpan 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; - } - - - /// - /// Checks if a path begins with / or \ and contains any of these patterns: - /// "/..\", "\..\", "\../", "\..0" where '0' is the null terminator. - /// - public static bool IsParentDirectoryPathReplacementNeeded(ReadOnlySpan 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 dest, ReadOnlySpan 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; - } - } -} diff --git a/src/LibHac/Fs/Common/PathUtility.cs b/src/LibHac/Fs/Common/PathUtility.cs index 0b27dcee..8fbbbde8 100644 --- a/src/LibHac/Fs/Common/PathUtility.cs +++ b/src/LibHac/Fs/Common/PathUtility.cs @@ -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 buffer, byte currentChar, byte newChar) @@ -68,62 +23,223 @@ namespace LibHac.Fs } } - /// - /// Performs the extra functions that nn::fs::FspPathPrintf does on the string buffer. - /// - /// The string builder to process. - /// The of the operation. - public static Result ToSfPath(in this U8StringBuilder builder) + public static bool IsCurrentDirectory(ReadOnlySpan 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 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 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 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 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 path) + { + Assert.SdkRequiresNotNull(path); + + uint utf8Buffer = 0; + Span utf8BufferSpan = SpanHelpers.AsByteSpan(ref utf8Buffer); + + ReadOnlySpan 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 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 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 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 lhs, ReadOnlySpan 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 path) + { + if (WindowsPath.IsWindowsPath(path, false)) + return true; + + return path.At(0) == DirectorySeparator; + } + + public static bool IsPathRelative(ReadOnlySpan path) + { + return path.At(0) != NullTerminator && !IsPathAbsolute(path); + } + + public static bool IsPathStartWithCurrentDirectory(ReadOnlySpan path) + { + return IsCurrentDirectory(path) || IsParentDirectory(path); + } } } diff --git a/src/LibHac/Fs/Common/PathUtility12.cs b/src/LibHac/Fs/Common/PathUtility12.cs deleted file mode 100644 index ebc41f87..00000000 --- a/src/LibHac/Fs/Common/PathUtility12.cs +++ /dev/null @@ -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 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 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 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 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 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 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 path) - { - Assert.SdkRequiresNotNull(path); - - uint utf8Buffer = 0; - Span utf8BufferSpan = SpanHelpers.AsByteSpan(ref utf8Buffer); - - ReadOnlySpan 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 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 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 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 lhs, ReadOnlySpan 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 path) - { - if (WindowsPath12.IsWindowsPath(path, false)) - return true; - - return path.At(0) == DirectorySeparator; - } - - public static bool IsPathRelative(ReadOnlySpan path) - { - return path.At(0) != NullTerminator && !IsPathAbsolute(path); - } - - public static bool IsPathStartWithCurrentDirectory(ReadOnlySpan path) - { - return IsCurrentDirectory(path) || IsParentDirectory(path); - } - } -} diff --git a/src/LibHac/Fs/Common/WindowsPath.cs b/src/LibHac/Fs/Common/WindowsPath.cs index 78eeae5a..0d0337e9 100644 --- a/src/LibHac/Fs/Common/WindowsPath.cs +++ b/src/LibHac/Fs/Common/WindowsPath.cs @@ -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 { + /// + /// Contains functions for working with Windows paths. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) 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 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 path) + private static bool IsUncPathImpl(ReadOnlySpan 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 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 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 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 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 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 path) + { + return IsUncPathImpl(path, true, true); + } + + public static bool IsUncPath(ReadOnlySpan path, bool checkForwardSlash, bool checkBackSlash) + { + return IsUncPathImpl(path, checkForwardSlash, checkBackSlash); + } + + public static int GetUncPathPrefixLength(ReadOnlySpan path) + { + return GetUncPathPrefixLengthImpl(path, true); + } + + public static bool IsDosDevicePath(ReadOnlySpan path) + { + return IsDosDevicePathImpl(path); } public static int GetDosDevicePathPrefixLength() @@ -84,37 +159,100 @@ namespace LibHac.Fs return DosDevicePathPrefixLength; } - public static bool IsDriveName(ReadOnlySpan path) + public static bool IsWindowsPath(ReadOnlySpan path, bool checkForwardSlash) { - return path.Length == WindowsDriveLength && path[1] == ':'; + return IsWindowsDrive(path) || IsDosDevicePath(path) || IsUncPath(path, checkForwardSlash, true); } - public static bool IsUncPath(ReadOnlySpan path) + public static int GetWindowsSkipLength(ReadOnlySpan 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 path) + public static bool IsDosDelimiterW(char c) { - int i; - for (i = 0; i < path.Length; i++) + return c == '/' || c == '\\'; + } + + public static bool IsWindowsDriveW(ReadOnlySpan 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 path) + { + return IsUncPathImpl(path, true, true); + } + + public static int GetUncPathPrefixLengthW(ReadOnlySpan path) + { + return GetUncPathPrefixLengthImpl(path, true); + } + + public static bool IsDosDevicePathW(ReadOnlySpan path) + { + return IsDosDevicePathImpl(path); + } + + public static bool IsWindowsPathW(ReadOnlySpan path) + { + return IsWindowsDriveW(path) || IsUncPathW(path) || IsDosDevicePathW(path); + } + + public static Result CheckCharacterCountForWindows(ReadOnlySpan path, int maxNameLength, int maxPathLength) + { + Assert.SdkRequiresNotNull(path); + + ReadOnlySpan 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 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; } } } diff --git a/src/LibHac/Fs/Common/WindowsPath12.cs b/src/LibHac/Fs/Common/WindowsPath12.cs deleted file mode 100644 index 02ce5bb6..00000000 --- a/src/LibHac/Fs/Common/WindowsPath12.cs +++ /dev/null @@ -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 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 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 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 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 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 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 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 path) - { - return IsUncPathImpl(path, true, true); - } - - public static bool IsUncPath(ReadOnlySpan path, bool checkForwardSlash, bool checkBackSlash) - { - return IsUncPathImpl(path, checkForwardSlash, checkBackSlash); - } - - public static int GetUncPathPrefixLength(ReadOnlySpan path) - { - return GetUncPathPrefixLengthImpl(path, true); - } - - public static bool IsDosDevicePath(ReadOnlySpan path) - { - return IsDosDevicePathImpl(path); - } - - public static int GetDosDevicePathPrefixLength() - { - return 4; - } - - public static bool IsWindowsPath(ReadOnlySpan path, bool checkForwardSlash) - { - return IsWindowsDrive(path) || IsDosDevicePath(path) || IsUncPath(path, checkForwardSlash, true); - } - - public static int GetWindowsSkipLength(ReadOnlySpan 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 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 path) - { - return IsUncPathImpl(path, true, true); - } - - public static int GetUncPathPrefixLengthW(ReadOnlySpan path) - { - return GetUncPathPrefixLengthImpl(path, true); - } - - public static bool IsDosDevicePathW(ReadOnlySpan path) - { - return IsDosDevicePathImpl(path); - } - - public static bool IsWindowsPathW(ReadOnlySpan path) - { - return IsWindowsDriveW(path) || IsUncPathW(path) || IsDosDevicePathW(path); - } - - public static Result CheckCharacterCountForWindows(ReadOnlySpan path, int maxNameLength, int maxPathLength) - { - Assert.SdkRequiresNotNull(path); - - ReadOnlySpan 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; - } - } -} diff --git a/src/LibHac/Fs/FileSystemClient.cs b/src/LibHac/Fs/FileSystemClient.cs index 99f3ec0e..d61cf1ca 100644 --- a/src/LibHac/Fs/FileSystemClient.cs +++ b/src/LibHac/Fs/FileSystemClient.cs @@ -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); } } diff --git a/src/LibHac/Fs/Fsa/MountUtility.cs b/src/LibHac/Fs/Fsa/MountUtility.cs index 8576857a..8b836121 100644 --- a/src/LibHac/Fs/Fsa/MountUtility.cs +++ b/src/LibHac/Fs/Fsa/MountUtility.cs @@ -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; diff --git a/src/LibHac/Fs/Shim/Bis.cs b/src/LibHac/Fs/Shim/Bis.cs index bf9932ff..3387f294 100644 --- a/src/LibHac/Fs/Shim/Bis.cs +++ b/src/LibHac/Fs/Shim/Bis.cs @@ -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 fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); diff --git a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs index 2c4938e0..14a63df7 100644 --- a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs +++ b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs @@ -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); diff --git a/src/LibHac/FsSrv/Impl/LocationResolverSet.cs b/src/LibHac/FsSrv/Impl/LocationResolverSet.cs index 56855ab5..37f4f458 100644 --- a/src/LibHac/FsSrv/Impl/LocationResolverSet.cs +++ b/src/LibHac/FsSrv/Impl/LocationResolverSet.cs @@ -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); } diff --git a/src/LibHac/FsSrv/Impl/PathNormalizer.cs b/src/LibHac/FsSrv/Impl/PathNormalizer.cs deleted file mode 100644 index ffc33d54..00000000 --- a/src/LibHac/FsSrv/Impl/PathNormalizer.cs +++ /dev/null @@ -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.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.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.Shared.Return(buffer); - } - } - - return Result.Success; - } - - [Flags] - public enum Option - { - None = 0, - PreserveUnc = (1 << 0), - PreserveTrailingSeparator = (1 << 1), - HasMountName = (1 << 2), - AcceptEmpty = (1 << 3) - } - } -} diff --git a/src/LibHac/FsSrv/NcaFileSystemService.cs b/src/LibHac/FsSrv/NcaFileSystemService.cs index 54cf8ff1..cd8aeb50 100644 --- a/src/LibHac/FsSrv/NcaFileSystemService.cs +++ b/src/LibHac/FsSrv/NcaFileSystemService.cs @@ -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 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, diff --git a/src/LibHac/FsSystem/LocalFileSystem.cs b/src/LibHac/FsSystem/LocalFileSystem.cs index 45afc0db..b50d60ec 100644 --- a/src/LibHac/FsSystem/LocalFileSystem.cs +++ b/src/LibHac/FsSystem/LocalFileSystem.cs @@ -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 diff --git a/src/LibHac/FsSystem/Save/SaveDataFileSystemCore.cs b/src/LibHac/FsSystem/Save/SaveDataFileSystemCore.cs index 8097420e..da09f40d 100644 --- a/src/LibHac/FsSystem/Save/SaveDataFileSystemCore.cs +++ b/src/LibHac/FsSystem/Save/SaveDataFileSystemCore.cs @@ -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) diff --git a/tests/LibHac.Tests/Fs/PathNormalizerTests.cs b/tests/LibHac.Tests/Fs/PathNormalizerTests.cs index 4b1c8d2e..595f6781 100644 --- a/tests/LibHac.Tests/Fs/PathNormalizerTests.cs +++ b/tests/LibHac.Tests/Fs/PathNormalizerTests.cs @@ -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); diff --git a/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp b/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp deleted file mode 100644 index d873f840..00000000 --- a/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp +++ /dev/null @@ -1,327 +0,0 @@ -// Uses GLoat to run code in nnsdk https://github.com/h1k421/GLoat -#include - -static char Buf[0x80000]; -static int BufPos = 0; - -static char ResultNameBuf[0x100]; - -namespace nn::fs::detail { - bool IsEnabledAccessLog(); -} - -namespace nn::fs::PathTool { - // SDK < 7 - nn::Result Normalize(char* buffer, uint64_t* outNormalizedPathLength, const char* path, uint64_t bufferLength, bool preserveUnc); - nn::Result IsNormalized(bool* outIsNormalized, const char* path); - - // SDK >= 7 < 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(); -} \ No newline at end of file diff --git a/tests/LibHac.Tests/Fs/PathToolTests.cs b/tests/LibHac.Tests/Fs/PathToolTests.cs deleted file mode 100644 index e2e6dd2c..00000000 --- a/tests/LibHac.Tests/Fs/PathToolTests.cs +++ /dev/null @@ -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} - }; - } -} diff --git a/tests/LibHac.Tests/Fs/PathUtilityTests.cs b/tests/LibHac.Tests/Fs/PathUtilityTests.cs index 063172af..3b264acb 100644 --- a/tests/LibHac.Tests/Fs/PathUtilityTests.cs +++ b/tests/LibHac.Tests/Fs/PathUtilityTests.cs @@ -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); } diff --git a/tests/LibHac.Tests/FsSrv/PathNormalizerTests.cs b/tests/LibHac.Tests/FsSrv/PathNormalizerTests.cs deleted file mode 100644 index 7cfea181..00000000 --- a/tests/LibHac.Tests/FsSrv/PathNormalizerTests.cs +++ /dev/null @@ -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); - } - } -}