Add PathNormalizer

This commit is contained in:
Alex Barney 2020-02-10 01:43:11 -07:00
parent cb6827e6c2
commit 1c28c08c94
7 changed files with 138 additions and 9 deletions

View File

@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace LibHac.Common
@ -76,6 +77,18 @@ namespace LibHac.Common
return new U8String(_buffer.ToArray());
}
public bool IsEmpty() => _buffer.IsEmpty;
/// <summary>
/// Checks if the <see cref="U8Span"/> has no buffer.
/// </summary>
/// <returns><see langword="true"/> if the span has no buffer.
/// Otherwise, <see langword="false"/>.</returns>
public bool IsNull() => _buffer.IsEmpty;
/// <summary>
/// Checks if the <see cref="U8Span"/> has no buffer or begins with a null terminator.
/// </summary>
/// <returns><see langword="true"/> if the span has no buffer or begins with a null terminator.
/// Otherwise, <see langword="false"/>.</returns>
public bool IsEmpty() => _buffer.IsEmpty || MemoryMarshal.GetReference(_buffer) == 0;
}
}

View File

@ -6,7 +6,7 @@ namespace LibHac.Fs
{
public static Result CheckMountName(U8Span name)
{
if (name.IsEmpty()) return ResultFs.NullArgument.Log();
if (name.IsNull()) return ResultFs.NullArgument.Log();
if (name.Length > 0 && name[0] == '@') return ResultFs.InvalidMountName.Log();
if (!CheckMountNameImpl(name)) return ResultFs.InvalidMountName.Log();
@ -16,7 +16,7 @@ namespace LibHac.Fs
public static Result CheckMountNameAcceptingReservedMountName(U8Span name)
{
if (name.IsEmpty()) return ResultFs.NullArgument.Log();
if (name.IsNull()) return ResultFs.NullArgument.Log();
if (!CheckMountNameImpl(name)) return ResultFs.InvalidMountName.Log();

View File

@ -360,7 +360,7 @@ namespace LibHac.Fs
}
}
if (path2.IsEmpty() || IsNullTerminator(path2[0]))
if (path2.IsEmpty())
return ResultFs.InvalidPathFormat.Log();
if (ContainsParentDirectoryAlt(path2))

View File

@ -1,26 +1,30 @@
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem;
namespace LibHac.FsService
{
public ref struct PathNormalizer
{
private U8Span _path;
private Result _result;
private readonly U8Span _path;
public U8Span Path => _path;
public Result Result { get; }
public PathNormalizer(U8Span path, Option option)
{
if (option.HasFlag(Option.AcceptEmpty) && path.IsEmpty())
{
_path = path;
_result = Result.Success;
Result = Result.Success;
}
else
{
bool preserveUnc = option.HasFlag(Option.PreserveUnc);
bool preserveTailSeparator = option.HasFlag(Option.PreserveTailSeparator);
bool hasMountName = option.HasFlag(Option.HasMountName);
_result = Normalize(out _path, path, preserveUnc, preserveTailSeparator, hasMountName);
Result = Normalize(out _path, path, preserveUnc, preserveTailSeparator, hasMountName);
}
}
@ -28,8 +32,33 @@ namespace LibHac.FsService
bool preserveTailSeparator, bool hasMountName)
{
normalizedPath = default;
throw new NotImplementedException();
Result rc = PathTool.IsNormalized(out bool isNormalized, path, preserveUnc, hasMountName);
if (rc.IsFailure()) return rc;
if (isNormalized)
{
normalizedPath = path;
}
else
{
var buffer = new byte[PathTools.MaxPathLength + 1];
rc = PathTool.Normalize(buffer, out long normalizedLength, path, preserveUnc, hasMountName);
if (rc.IsFailure()) return rc;
// GetLength is capped at MaxPathLength bytes to leave room for the null terminator
if (preserveTailSeparator &&
PathTool.IsSeparator(path[StringUtils.GetLength(path, PathTools.MaxPathLength) - 1]))
{
buffer[(int)normalizedLength] = StringTraits.DirectorySeparator;
buffer[(int)normalizedLength + 1] = StringTraits.NullTerminator;
}
normalizedPath = new U8Span(buffer);
}
return Result.Success;
}
[Flags]

View File

@ -148,6 +148,7 @@ void CreateNormalizationTestData(void (*func)(char const*, bool, bool)) {
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);

View File

@ -75,6 +75,7 @@ namespace LibHac.Tests.Fs
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},
@ -464,6 +465,7 @@ namespace LibHac.Tests.Fs
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},

View File

@ -0,0 +1,84 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsService;
using Xunit;
namespace LibHac.Tests.FsService
{
public class PathNormalizerTests
{
[Fact]
public static void Ctor_EmptyPathWithAcceptEmptyOption_ReturnsEmptyPathWithSuccess()
{
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()
{
var normalizer = new PathNormalizer("/a/./b/".ToU8Span(), PathNormalizer.Option.PreserveTailSeparator);
Assert.Equal(Result.Success, normalizer.Result);
Assert.Equal("/a/b/", normalizer.Path.ToString());
}
[Fact]
public static void Normalize_PreserveTailSeparatorOption_IgnoresMissingTailSeparator()
{
var normalizer = new PathNormalizer("/a/./b".ToU8Span(), PathNormalizer.Option.PreserveTailSeparator);
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();
var normalizer = new PathNormalizer(originalPath, PathNormalizer.Option.PreserveTailSeparator);
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()
{
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()
{
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()
{
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()
{
var normalizer = new PathNormalizer("mount:/a/./b".ToU8Span(), PathNormalizer.Option.None);
Assert.Equal(ResultFs.InvalidPathFormat.Value, normalizer.Result);
}
}
}