mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Make path normalizer work with mount names
This commit is contained in:
parent
5a8744c6b5
commit
a44bdf780e
3 changed files with 235 additions and 51 deletions
|
@ -2,85 +2,189 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
using static LibHac.Results;
|
||||||
|
using static LibHac.Fs.ResultsFs;
|
||||||
|
|
||||||
namespace LibHac.Fs
|
namespace LibHac.Fs
|
||||||
{
|
{
|
||||||
public static class PathTools
|
public static class PathTools
|
||||||
{
|
{
|
||||||
public static readonly char DirectorySeparator = '/';
|
public static readonly char DirectorySeparator = '/';
|
||||||
|
public static readonly char MountSeparator = ':';
|
||||||
|
internal const int MountNameLength = 0xF;
|
||||||
|
|
||||||
public static string Normalize(string inPath)
|
public static string Normalize(string inPath)
|
||||||
{
|
{
|
||||||
if (IsNormalized(inPath.AsSpan())) return inPath;
|
if (IsNormalized(inPath.AsSpan())) return inPath;
|
||||||
return NormalizeInternal(inPath);
|
|
||||||
|
Span<char> initialBuffer = stackalloc char[0x200];
|
||||||
|
var sb = new ValueStringBuilder(initialBuffer);
|
||||||
|
|
||||||
|
int rootLen = 0;
|
||||||
|
int maxMountLen = Math.Min(inPath.Length, MountNameLength);
|
||||||
|
|
||||||
|
for (int i = 0; i < maxMountLen; i++)
|
||||||
|
{
|
||||||
|
if (inPath[i] == MountSeparator)
|
||||||
|
{
|
||||||
|
rootLen = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsDirectorySeparator(inPath[i]))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isNormalized = NormalizeInternal(inPath.AsSpan(), rootLen, ref sb);
|
||||||
|
|
||||||
|
string normalized = isNormalized ? inPath : sb.ToString();
|
||||||
|
|
||||||
|
sb.Dispose();
|
||||||
|
|
||||||
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
public static string NormalizeInternal(string inPath)
|
internal static bool NormalizeInternal(ReadOnlySpan<char> path, int rootLength, ref ValueStringBuilder sb)
|
||||||
{
|
{
|
||||||
// Relative paths aren't a thing for IFileSystem, so assume all paths are absolute
|
if (rootLength > 0)
|
||||||
// and add a '/' to the beginning of the path if it doesn't already begin with one
|
{
|
||||||
if (inPath.Length == 0 || !IsDirectorySeparator(inPath[0])) inPath = DirectorySeparator + inPath;
|
sb.Append(path.Slice(0, rootLength));
|
||||||
|
}
|
||||||
|
|
||||||
ReadOnlySpan<char> path = inPath.AsSpan();
|
bool isNormalized = true;
|
||||||
|
|
||||||
if (path.Length == 0) return DirectorySeparator.ToString();
|
var state = NormalizeState.Initial;
|
||||||
|
|
||||||
Span<char> initialBuffer = stackalloc char[0x200];
|
for (int i = rootLength; i < path.Length; i++)
|
||||||
var sb = new ValueStringBuilder(initialBuffer);
|
|
||||||
|
|
||||||
for (int i = 0; i < path.Length; i++)
|
|
||||||
{
|
{
|
||||||
char c = path[i];
|
char c = path[i];
|
||||||
|
|
||||||
if (IsDirectorySeparator(c) && i + 1 < path.Length)
|
switch (state)
|
||||||
{
|
{
|
||||||
// Skip this character if it's a directory separator and if the next character is, too,
|
case NormalizeState.Initial when IsDirectorySeparator(c):
|
||||||
// e.g. "parent//child" => "parent/child"
|
state = NormalizeState.Delimiter;
|
||||||
if (IsDirectorySeparator(path[i + 1])) continue;
|
sb.Append(c);
|
||||||
|
break;
|
||||||
|
|
||||||
// Skip this character and the next if it's referring to the current directory,
|
case NormalizeState.Initial when c == '.':
|
||||||
// e.g. "parent/./child" => "parent/child"
|
isNormalized = false;
|
||||||
if (IsCurrentDirectory(path, i))
|
state = NormalizeState.Dot;
|
||||||
{
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip this character and the next two if it's referring to the parent directory,
|
sb.Append(DirectorySeparator);
|
||||||
// e.g. "parent/child/../grandchild" => "parent/grandchild"
|
sb.Append(c);
|
||||||
if (IsParentDirectory(path, i))
|
break;
|
||||||
{
|
|
||||||
// Unwind back to the last slash (and if there isn't one, clear out everything).
|
case NormalizeState.Initial:
|
||||||
for (int s = sb.Length - 1; s >= 0; s--)
|
isNormalized = false;
|
||||||
|
state = NormalizeState.Delimiter;
|
||||||
|
|
||||||
|
sb.Append(DirectorySeparator);
|
||||||
|
sb.Append(c);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NormalizeState.Normal when IsDirectorySeparator(c):
|
||||||
|
state = NormalizeState.Delimiter;
|
||||||
|
sb.Append(c);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NormalizeState.Delimiter when IsDirectorySeparator(c):
|
||||||
|
isNormalized = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NormalizeState.Delimiter when c == '.':
|
||||||
|
state = NormalizeState.Dot;
|
||||||
|
sb.Append(c);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NormalizeState.Delimiter:
|
||||||
|
state = NormalizeState.Normal;
|
||||||
|
sb.Append(c);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NormalizeState.Dot when IsDirectorySeparator(c):
|
||||||
|
isNormalized = false;
|
||||||
|
state = NormalizeState.Delimiter;
|
||||||
|
sb.Length -= 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NormalizeState.Dot when c == '.':
|
||||||
|
state = NormalizeState.DoubleDot;
|
||||||
|
sb.Append(c);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NormalizeState.Dot:
|
||||||
|
state = NormalizeState.Normal;
|
||||||
|
sb.Append(c);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NormalizeState.DoubleDot when IsDirectorySeparator(c):
|
||||||
|
isNormalized = false;
|
||||||
|
state = NormalizeState.Delimiter;
|
||||||
|
|
||||||
|
int s = sb.Length - 1;
|
||||||
|
int separators = 0;
|
||||||
|
|
||||||
|
for (; s > rootLength; s--)
|
||||||
{
|
{
|
||||||
if (IsDirectorySeparator(sb[s]))
|
if (IsDirectorySeparator(sb[s]))
|
||||||
{
|
{
|
||||||
sb.Length = s;
|
separators++;
|
||||||
|
|
||||||
|
if (separators == 2) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Length = s + 1;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NormalizeState.DoubleDot:
|
||||||
|
state = NormalizeState.Normal;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i += 2;
|
switch (state)
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.Append(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we haven't changed the source path, return the original
|
|
||||||
if (sb.Length == inPath.Length)
|
|
||||||
{
|
{
|
||||||
return inPath;
|
case NormalizeState.Dot:
|
||||||
|
isNormalized = false;
|
||||||
|
sb.Length -= 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NormalizeState.DoubleDot:
|
||||||
|
isNormalized = false;
|
||||||
|
|
||||||
|
int s = sb.Length - 1;
|
||||||
|
int separators = 0;
|
||||||
|
|
||||||
|
for (; s > rootLength; s--)
|
||||||
|
{
|
||||||
|
if (IsDirectorySeparator(sb[s]))
|
||||||
|
{
|
||||||
|
separators++;
|
||||||
|
|
||||||
|
if (separators == 2) break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sb.Length == 0)
|
sb.Length = s;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sb.Length == rootLength)
|
||||||
{
|
{
|
||||||
sb.Append(DirectorySeparator);
|
sb.Append(DirectorySeparator);
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.ToString();
|
return isNormalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetParentDirectory(string path)
|
public static string GetParentDirectory(string path)
|
||||||
|
@ -240,19 +344,21 @@ namespace LibHac.Fs
|
||||||
return c == DirectorySeparator;
|
return c == DirectorySeparator;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
public static Result GetMountName(string path, out string mountName)
|
||||||
internal static bool IsCurrentDirectory(ReadOnlySpan<char> path, int index)
|
|
||||||
{
|
{
|
||||||
return (index + 2 == path.Length || IsDirectorySeparator(path[index + 2])) &&
|
int maxLen = Math.Min(path.Length, MountNameLength);
|
||||||
path[index + 1] == '.';
|
|
||||||
|
for (int i = 0; i < maxLen; i++)
|
||||||
|
{
|
||||||
|
if (path[i] == MountSeparator)
|
||||||
|
{
|
||||||
|
mountName = path.Substring(0, i);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
mountName = default;
|
||||||
internal static bool IsParentDirectory(ReadOnlySpan<char> path, int index)
|
return ResultFsInvalidMountName;
|
||||||
{
|
|
||||||
return index + 2 < path.Length &&
|
|
||||||
(index + 3 == path.Length || IsDirectorySeparator(path[index + 3])) &&
|
|
||||||
path[index + 1] == '.' && path[index + 2] == '.';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum NormalizeState
|
private enum NormalizeState
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
public const int ModuleFs = 2;
|
public const int ModuleFs = 2;
|
||||||
|
|
||||||
public static Result ResultFsMountNameAlreadyExists => new Result(ModuleFs, 60);
|
public static Result ResultFsMountNameAlreadyExists => new Result(ModuleFs, 60);
|
||||||
|
public static Result ResultFsInvalidMountName => new Result(ModuleFs, 6065);
|
||||||
public static Result ResultFsWritableFileOpen => new Result(ModuleFs, 6457);
|
public static Result ResultFsWritableFileOpen => new Result(ModuleFs, 6457);
|
||||||
public static Result ResultFsMountNameNotFound => new Result(ModuleFs, 6905);
|
public static Result ResultFsMountNameNotFound => new Result(ModuleFs, 6905);
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,17 @@ namespace LibHac.Tests
|
||||||
new object[] {"..", "/"},
|
new object[] {"..", "/"},
|
||||||
new object[] {"../a/b/c/.", "/a/b/c"},
|
new object[] {"../a/b/c/.", "/a/b/c"},
|
||||||
new object[] {"./a/b/c/.", "/a/b/c"},
|
new object[] {"./a/b/c/.", "/a/b/c"},
|
||||||
|
|
||||||
|
new object[] {"a:/a/b/c", "a:/a/b/c"},
|
||||||
|
new object[] {"mount:/a/b/../c", "mount:/a/c"},
|
||||||
|
new object[] {"mount:", "mount:/"},
|
||||||
|
new object[] {"abc:/a/../../../a/b/c", "abc:/a/b/c"},
|
||||||
|
new object[] {"abc:/./b/../c/", "abc:/c/"},
|
||||||
|
new object[] {"abc:/.", "abc:/"},
|
||||||
|
new object[] {"abc:/..", "abc:/"},
|
||||||
|
new object[] {"abc:/", "abc:/"},
|
||||||
|
new object[] {"abc://a/b//.//c", "abc:/a/b/c"},
|
||||||
|
new object[] {"abc:/././/././a/b//.//c", "abc:/a/b/c"},
|
||||||
};
|
};
|
||||||
|
|
||||||
public static object[][] SubPathTestItems =
|
public static object[][] SubPathTestItems =
|
||||||
|
@ -56,6 +67,63 @@ namespace LibHac.Tests
|
||||||
new object[] {"/a/b/c/", "/a/b/cd", false},
|
new object[] {"/a/b/c/", "/a/b/cd", false},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static object[][] IsNormalizedTestItems =
|
||||||
|
{
|
||||||
|
new object[] {"", "/"},
|
||||||
|
new object[] {"/"},
|
||||||
|
new object[] {"/a/b/c"},
|
||||||
|
new object[] {"/a/c"},
|
||||||
|
new object[] {"/a/b"},
|
||||||
|
new object[] {"/a/b/c"},
|
||||||
|
new object[] {"/"},
|
||||||
|
new object[] {"/a/b/c"},
|
||||||
|
|
||||||
|
new object[] {"/a/b/c/"},
|
||||||
|
new object[] {"/a/c/"},
|
||||||
|
new object[] {"/c/"},
|
||||||
|
|
||||||
|
new object[] {"/a"},
|
||||||
|
|
||||||
|
new object[] {"a:/a/b/c"},
|
||||||
|
new object[] {"mount:/a/c"},
|
||||||
|
new object[] {"mount:/"},
|
||||||
|
};
|
||||||
|
|
||||||
|
public static object[][] IsNotNormalizedTestItems =
|
||||||
|
{
|
||||||
|
new object[] {""},
|
||||||
|
new object[] {"/."},
|
||||||
|
new object[] {"/a/b/../c", "/a/c"},
|
||||||
|
new object[] {"/a/b/c/..", "/a/b"},
|
||||||
|
new object[] {"/a/b/c/.", "/a/b/c"},
|
||||||
|
new object[] {"/a/../../..", "/"},
|
||||||
|
new object[] {"/a/../../../a/b/c", "/a/b/c"},
|
||||||
|
new object[] {"//a/b//.//c", "/a/b/c"},
|
||||||
|
new object[] {"/../a/b/c/.", "/a/b/c"},
|
||||||
|
new object[] {"/./a/b/c/.", "/a/b/c"},
|
||||||
|
|
||||||
|
new object[] {"/a/b/c/", "/a/b/c/"},
|
||||||
|
new object[] {"/a/./b/../c/", "/a/c/"},
|
||||||
|
new object[] {"/./b/../c/", "/c/"},
|
||||||
|
new object[] {"/a/../../../", "/"},
|
||||||
|
new object[] {"//a/b//.//c/", "/a/b/c/"},
|
||||||
|
new object[] {"/tmp/../", "/"},
|
||||||
|
|
||||||
|
new object[] {"a", "/a"},
|
||||||
|
new object[] {"a/../../../a/b/c", "/a/b/c"},
|
||||||
|
new object[] {"./b/../c/", "/c/"},
|
||||||
|
new object[] {".", "/"},
|
||||||
|
new object[] {"..", "/"},
|
||||||
|
new object[] {"../a/b/c/.", "/a/b/c"},
|
||||||
|
new object[] {"./a/b/c/.", "/a/b/c"},
|
||||||
|
|
||||||
|
new object[] {"a:/a/b/c", "a:/a/b/c"},
|
||||||
|
new object[] {"mount:/a/b/../c", "mount:/a/c"},
|
||||||
|
new object[] {"mount:/a/b/../c", "mount:/a/c"},
|
||||||
|
new object[] {"mount:", "mount:/"},
|
||||||
|
new object[] {"abc:/a/../../../a/b/c", "abc:/a/b/c"},
|
||||||
|
};
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(NormalizedPathTestItems))]
|
[MemberData(nameof(NormalizedPathTestItems))]
|
||||||
public static void NormalizePath(string path, string expected)
|
public static void NormalizePath(string path, string expected)
|
||||||
|
@ -65,6 +133,15 @@ namespace LibHac.Tests
|
||||||
Assert.Equal(expected, actual);
|
Assert.Equal(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(NormalizedPathTestItems))]
|
||||||
|
public static void IsNormalized(string path, string expected)
|
||||||
|
{
|
||||||
|
string actual = PathTools.Normalize(path);
|
||||||
|
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(SubPathTestItems))]
|
[MemberData(nameof(SubPathTestItems))]
|
||||||
public static void TestSubPath(string rootPath, string path, bool expected)
|
public static void TestSubPath(string rootPath, string path, bool expected)
|
||||||
|
|
Loading…
Reference in a new issue