Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/duplicati/duplicati.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDean Ferreyra <dean@octw.com>2021-11-08 01:05:39 +0300
committerDean Ferreyra <dean@octw.com>2021-11-08 18:42:07 +0300
commit459d1f27f619e5f3c7ac4e012efa30f2f48b5aba (patch)
tree5069d135a132c9b81072e248c2c20587dcd99c13 /Duplicati
parent2f5bb306bd3f4c2c05201ae76d0d72afa9b9a66b (diff)
Formalize inclusion of code from https://github.com/dotnet
Fix SystemIOWindows.PathGetFullPath per review comments. Add unit tests for SystemIOWindows.PathGetFullPath and SystemIOWindows.PrefixWithUNC. Also, fix a bug in the SystemIOWindows.PrefixWithUNC change that was exposed by these new unit tests. In BackendToolTests, remove unused [Setup] and [TearDown] methods.
Diffstat (limited to 'Duplicati')
-rw-r--r--Duplicati/Library/Common/Duplicati.Library.Common.csproj2
-rwxr-xr-xDuplicati/Library/Common/IO/DotNetRuntime.System.IO.Path.Windows.cs59
-rwxr-xr-xDuplicati/Library/Common/IO/DotNetRuntime.System.IO.PathInternal.Windows.cs133
-rw-r--r--Duplicati/Library/Common/IO/SystemIOWindows.cs103
-rwxr-xr-xDuplicati/UnitTest/BackendToolTests.cs12
-rw-r--r--Duplicati/UnitTest/IOTests.cs452
6 files changed, 664 insertions, 97 deletions
diff --git a/Duplicati/Library/Common/Duplicati.Library.Common.csproj b/Duplicati/Library/Common/Duplicati.Library.Common.csproj
index 6fc16df6a..e85ee6a2d 100644
--- a/Duplicati/Library/Common/Duplicati.Library.Common.csproj
+++ b/Duplicati/Library/Common/Duplicati.Library.Common.csproj
@@ -62,6 +62,8 @@
<Reference Include="System.Transactions" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="IO\DotNetRuntime.System.IO.Path.Windows.cs" />
+ <Compile Include="IO\DotNetRuntime.System.IO.PathInternal.Windows.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Platform\Platform.cs" />
<Compile Include="IO\SystemIO.cs" />
diff --git a/Duplicati/Library/Common/IO/DotNetRuntime.System.IO.Path.Windows.cs b/Duplicati/Library/Common/IO/DotNetRuntime.System.IO.Path.Windows.cs
new file mode 100755
index 000000000..6a830a955
--- /dev/null
+++ b/Duplicati/Library/Common/IO/DotNetRuntime.System.IO.Path.Windows.cs
@@ -0,0 +1,59 @@
+// Adapted from https://raw.githubusercontent.com/dotnet/runtime/v5.0.12/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs
+
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation and Contributors
+//
+// All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using System;
+
+namespace Duplicati.Library.Common.IO
+{
+ public static class DotNetRuntimePathWindows
+ {
+ /// <summary>
+ /// Returns true if the path is fixed to a specific drive or UNC path. This method does no
+ /// validation of the path (URIs will be returned as relative as a result).
+ /// Returns false if the path specified is relative to the current drive or working directory.
+ /// </summary>
+ /// <remarks>
+ /// Handles paths that use the alternate directory separator. It is a frequent mistake to
+ /// assume that rooted paths <see cref="Path.IsPathRooted(string)"/> are not relative. This isn't the case.
+ /// "C:a" is drive relative- meaning that it will be resolved against the current directory
+ /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
+ /// will not be used to modify the path).
+ /// </remarks>
+ /// <exception cref="ArgumentNullException">
+ /// Thrown if <paramref name="path"/> is null.
+ /// </exception>
+ public static bool IsPathFullyQualified(string path)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+
+ return !PathInternalWindows.IsPartiallyQualified(path);
+ }
+ }
+}
diff --git a/Duplicati/Library/Common/IO/DotNetRuntime.System.IO.PathInternal.Windows.cs b/Duplicati/Library/Common/IO/DotNetRuntime.System.IO.PathInternal.Windows.cs
new file mode 100755
index 000000000..75a117392
--- /dev/null
+++ b/Duplicati/Library/Common/IO/DotNetRuntime.System.IO.PathInternal.Windows.cs
@@ -0,0 +1,133 @@
+// Adapted from https://github.com/dotnet/runtime/blob/v5.0.12/src/libraries/Common/src/System/IO/PathInternal.Windows.cs
+
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation and Contributors
+//
+// All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using System.Runtime.CompilerServices;
+using System.IO;
+
+namespace Duplicati.Library.Common.IO
+{
+ /// <summary>Contains internal path helpers that are shared between many projects.</summary>
+ internal static class PathInternalWindows
+ {
+ // All paths in Win32 ultimately end up becoming a path to a File object in the Windows object manager. Passed in paths get mapped through
+ // DosDevice symbolic links in the object tree to actual File objects under \Devices. To illustrate, this is what happens with a typical
+ // path "Foo" passed as a filename to any Win32 API:
+ //
+ // 1. "Foo" is recognized as a relative path and is appended to the current directory (say, "C:\" in our example)
+ // 2. "C:\Foo" is prepended with the DosDevice namespace "\??\"
+ // 3. CreateFile tries to create an object handle to the requested file "\??\C:\Foo"
+ // 4. The Object Manager recognizes the DosDevices prefix and looks
+ // a. First in the current session DosDevices ("\Sessions\1\DosDevices\" for example, mapped network drives go here)
+ // b. If not found in the session, it looks in the Global DosDevices ("\GLOBAL??\")
+ // 5. "C:" is found in DosDevices (in our case "\GLOBAL??\C:", which is a symbolic link to "\Device\HarddiskVolume6")
+ // 6. The full path is now "\Device\HarddiskVolume6\Foo", "\Device\HarddiskVolume6" is a File object and parsing is handed off
+ // to the registered parsing method for Files
+ // 7. The registered open method for File objects is invoked to create the file handle which is then returned
+ //
+ // There are multiple ways to directly specify a DosDevices path. The final format of "\??\" is one way. It can also be specified
+ // as "\\.\" (the most commonly documented way) and "\\?\". If the question mark syntax is used the path will skip normalization
+ // (essentially GetFullPathName()) and path length checks.
+
+ // Windows Kernel-Mode Object Manager
+ // https://msdn.microsoft.com/en-us/library/windows/hardware/ff565763.aspx
+ // https://channel9.msdn.com/Shows/Going+Deep/Windows-NT-Object-Manager
+ //
+ // Introduction to MS-DOS Device Names
+ // https://msdn.microsoft.com/en-us/library/windows/hardware/ff548088.aspx
+ //
+ // Local and Global MS-DOS Device Names
+ // https://msdn.microsoft.com/en-us/library/windows/hardware/ff554302.aspx
+
+ internal const string ExtendedDevicePathPrefix = @"\\?\";
+ internal const string UncPathPrefix = @"\\";
+ internal const string UncDevicePrefixToInsert = @"?\UNC\";
+ internal const string UncExtendedPathPrefix = @"\\?\UNC\";
+ internal const string DevicePathPrefix = @"\\.\";
+
+ internal const int MaxShortPath = 260;
+
+ // \\?\, \\.\, \??\
+ internal const int DevicePrefixLength = 4;
+
+ /// <summary>
+ /// Returns true if the given character is a valid drive letter
+ /// </summary>
+ internal static bool IsValidDriveChar(char value)
+ {
+ return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'));
+ }
+
+ /// <summary>
+ /// Returns true if the path specified is relative to the current drive or working directory.
+ /// Returns false if the path is fixed to a specific drive or UNC path. This method does no
+ /// validation of the path (URIs will be returned as relative as a result).
+ /// </summary>
+ /// <remarks>
+ /// Handles paths that use the alternate directory separator. It is a frequent mistake to
+ /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case.
+ /// "C:a" is drive relative- meaning that it will be resolved against the current directory
+ /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
+ /// will not be used to modify the path).
+ /// </remarks>
+ internal static bool IsPartiallyQualified(string path)
+ {
+ if (path.Length < 2)
+ {
+ // It isn't fixed, it must be relative. There is no way to specify a fixed
+ // path with one character (or less).
+ return true;
+ }
+
+ if (IsDirectorySeparator(path[0]))
+ {
+ // There is no valid way to specify a relative path with two initial slashes or
+ // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
+ return !(path[1] == '?' || IsDirectorySeparator(path[1]));
+ }
+
+ // The only way to specify a fixed path that doesn't begin with two slashes
+ // is the drive, colon, slash format- i.e. C:\
+ return !((path.Length >= 3)
+ && (path[1] == Path.VolumeSeparatorChar)
+ && IsDirectorySeparator(path[2])
+ // To match old behavior we'll check the drive character for validity as the path is technically
+ // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.
+ && IsValidDriveChar(path[0]));
+ }
+
+ /// <summary>
+ /// True if the given character is a directory separator.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static bool IsDirectorySeparator(char c)
+ {
+ return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
+ }
+ }
+}
diff --git a/Duplicati/Library/Common/IO/SystemIOWindows.cs b/Duplicati/Library/Common/IO/SystemIOWindows.cs
index f1486c154..53a0ceb33 100644
--- a/Duplicati/Library/Common/IO/SystemIOWindows.cs
+++ b/Duplicati/Library/Common/IO/SystemIOWindows.cs
@@ -29,9 +29,9 @@ namespace Duplicati.Library.Common.IO
{
public struct SystemIOWindows : ISystemIO
{
- private const string UNCPREFIX = @"\\?\";
- private const string UNCPREFIX_SERVER = @"\\?\UNC\";
- private const string PATHPREFIX_SERVER = @"\\";
+ private const string UNCPREFIX = PathInternalWindows.ExtendedDevicePathPrefix;
+ private const string UNCPREFIX_SERVER = PathInternalWindows.UncExtendedPathPrefix;
+ private const string PATHPREFIX_SERVER = PathInternalWindows.UncPathPrefix;
private const string PATHPREFIX_SERVER_ALT = @"//";
private static readonly string DIRSEP = Util.DirectorySeparatorString;
@@ -47,12 +47,12 @@ namespace Duplicati.Library.Common.IO
// For example: \\?\C:\Temp\foo.txt or \\?\UNC\example.com\share\foo.txt
return path;
}
- else if (IsPrefixedWithBasicUNC(path))
+ else if (IsPrefixedWithBasicUNC(path) && !HasRelativePathComponents(path))
{
// For example: \\example.com\share\foo.txt or //example.com/share/foo.txt
return UNCPREFIX_SERVER + ConvertSlashes(path.Substring(PATHPREFIX_SERVER.Length));
}
- else if (IsPathFullyQualified(path) && !HasRelativePathComponents(path))
+ else if (DotNetRuntimePathWindows.IsPathFullyQualified(path) && !HasRelativePathComponents(path))
{
// For example: C:\Temp\foo.txt or C:/Temp/foo.txt
return UNCPREFIX + ConvertSlashes(path);
@@ -230,85 +230,6 @@ namespace Duplicati.Library.Common.IO
System.IO.Directory.SetAccessControl(PrefixWithUNC(path), rules);
}
- #region Adapted from https://github.com/dotnet/runtime (MIT license)
- /// <summary>
- /// Returns true if the path is fixed to a specific drive or UNC path. This method does no
- /// validation of the path (URIs will be returned as relative as a result).
- /// Returns false if the path specified is relative to the current drive or working directory.
- /// </summary>
- /// <remarks>
- /// Handles paths that use the alternate directory separator. It is a frequent mistake to
- /// assume that rooted paths <see cref="Path.IsPathRooted(string)"/> are not relative. This isn't the case.
- /// "C:a" is drive relative- meaning that it will be resolved against the current directory
- /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
- /// will not be used to modify the path).
- /// </remarks>
- /// <exception cref="ArgumentNullException">
- /// Thrown if <paramref name="path"/> is null.
- /// </exception>
- public static bool IsPathFullyQualified(string path)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
-
- return !IsPartiallyQualified(path);
- }
-
- /// <summary>
- /// Returns true if the path specified is relative to the current drive or working directory.
- /// Returns false if the path is fixed to a specific drive or UNC path. This method does no
- /// validation of the path (URIs will be returned as relative as a result).
- /// </summary>
- /// <remarks>
- /// Handles paths that use the alternate directory separator. It is a frequent mistake to
- /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case.
- /// "C:a" is drive relative- meaning that it will be resolved against the current directory
- /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
- /// will not be used to modify the path).
- /// </remarks>
- internal static bool IsPartiallyQualified(string path)
- {
- if (path.Length < 2)
- {
- // It isn't fixed, it must be relative. There is no way to specify a fixed
- // path with one character (or less).
- return true;
- }
-
- if (IsDirectorySeparator(path[0]))
- {
- // There is no valid way to specify a relative path with two initial slashes or
- // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
- return !(path[1] == '?' || IsDirectorySeparator(path[1]));
- }
-
- // The only way to specify a fixed path that doesn't begin with two slashes
- // is the drive, colon, slash format- i.e. C:\
- return !((path.Length >= 3)
- && (path[1] == Path.VolumeSeparatorChar)
- && IsDirectorySeparator(path[2])
- // To match old behavior we'll check the drive character for validity as the path is technically
- // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.
- && IsValidDriveChar(path[0]));
- }
-
- /// <summary>
- /// True if the given character is a directory separator.
- /// </summary>
- internal static bool IsDirectorySeparator(char c)
- {
- return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
- }
-
- /// <summary>
- /// Returns true if the given character is a valid drive letter
- /// </summary>
- internal static bool IsValidDriveChar(char value)
- {
- return (value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z');
- }
- #endregion
-
#region ISystemIO implementation
public void DirectoryCreate(string path)
{
@@ -539,7 +460,19 @@ namespace Duplicati.Library.Common.IO
public string PathGetFullPath(string path)
{
- return PrefixWithUNC(Path.GetFullPath(path));
+ // Desired behavior:
+ // 1. If path is already prefixed with \\?\, it should be left untouched
+ // 2. If path is not already prefixed with \\?\, the return value should also not be prefixed
+ // 3. If path is relative or has relative components, that should be resolved by calling Path.GetFullPath()
+ // 4. If path is not relative and has no relative components, prefix with \\?\ to prevent normalization from munging "problematic Windows paths"
+ if (IsPrefixedWithUNC(path))
+ {
+ return path;
+ }
+ else
+ {
+ return StripUNCPrefix(Path.GetFullPath(PrefixWithUNC(path)));
+ }
}
public IFileEntry DirectoryEntry(string path)
diff --git a/Duplicati/UnitTest/BackendToolTests.cs b/Duplicati/UnitTest/BackendToolTests.cs
index 94616cccf..6c4b17122 100755
--- a/Duplicati/UnitTest/BackendToolTests.cs
+++ b/Duplicati/UnitTest/BackendToolTests.cs
@@ -11,18 +11,6 @@ namespace Duplicati.UnitTest
[TestFixture]
public class BackendToolTests : BasicSetupHelper
{
- [SetUp]
- public override void SetUp()
- {
- base.SetUp();
- }
-
- [TearDown]
- public override void TearDown()
- {
- base.TearDown();
- }
-
[Test]
[Category("BackendTool")]
public void Get()
diff --git a/Duplicati/UnitTest/IOTests.cs b/Duplicati/UnitTest/IOTests.cs
index 48b4ef5ca..84940d2b1 100644
--- a/Duplicati/UnitTest/IOTests.cs
+++ b/Duplicati/UnitTest/IOTests.cs
@@ -95,5 +95,457 @@ namespace Duplicati.UnitTest
//In particular don't throw PathTooLongException
Assert.Throws<System.IO.DirectoryNotFoundException>(() => SystemIO.IO_OS.GetDirectories(longPath));
}
+
+ [Test]
+ public void TestPrefixWithUNCInWindowsClient()
+ {
+ if (!Platform.IsClientWindows)
+ {
+ return;
+ }
+
+ // Normalization of basic relative paths, with both kinds of slashes
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"."),
+ Is.EqualTo(@"."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"temp"),
+ Is.EqualTo(@"temp"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"temp\file.txt"),
+ Is.EqualTo(@"temp\file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\"),
+ Is.EqualTo(@"\"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"/"),
+ Is.EqualTo(@"/"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\temp"),
+ Is.EqualTo(@"\temp"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"/temp"),
+ Is.EqualTo(@"/temp"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"/temp/file.txt"),
+ Is.EqualTo(@"/temp/file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"/temp/file.txt"),
+ Is.EqualTo(@"/temp/file.txt"));
+
+ // Normalization of full qualified paths, but with relative components, with both kinds of slashes
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:\temp\."),
+ Is.EqualTo(@"C:\temp\."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:/temp/."),
+ Is.EqualTo(@"C:/temp/."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:\temp\.."),
+ Is.EqualTo(@"C:\temp\.."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:/temp/.."),
+ Is.EqualTo(@"C:/temp/.."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:\temp\..\folder"),
+ Is.EqualTo(@"C:\temp\..\folder"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:/temp/../folder"),
+ Is.EqualTo(@"C:/temp/../folder"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:\temp\.\file.txt"),
+ Is.EqualTo(@"C:\temp\.\file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:/temp/./file.txt"),
+ Is.EqualTo(@"C:/temp/./file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\example.com\share\."),
+ Is.EqualTo(@"\\example.com\share\."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"//example.com/share/."),
+ Is.EqualTo(@"//example.com/share/."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\example.com\share\.."),
+ Is.EqualTo(@"\\example.com\share\.."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"//example.com/share/.."),
+ Is.EqualTo(@"//example.com/share/.."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\example.com\share\..\folder"),
+ Is.EqualTo(@"\\example.com\share\..\folder"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"//example.com/share/../folder"),
+ Is.EqualTo(@"//example.com/share/../folder"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\example.com\share\.\file.txt"),
+ Is.EqualTo(@"\\example.com\share\.\file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"//example.com/share/./file.txt"),
+ Is.EqualTo(@"//example.com/share/./file.txt"));
+
+ // Fully qualified paths with no relative components, with both kinds of slashes
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:\"),
+ Is.EqualTo(@"\\?\C:\"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:/"),
+ Is.EqualTo(@"\\?\C:\"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:\temp"),
+ Is.EqualTo(@"\\?\C:\temp"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:/temp"),
+ Is.EqualTo(@"\\?\C:\temp"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:\temp\file.txt"),
+ Is.EqualTo(@"\\?\C:\temp\file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:/temp/file.txt"),
+ Is.EqualTo(@"\\?\C:\temp\file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\example.com\share"),
+ Is.EqualTo(@"\\?\UNC\example.com\share"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"//example.com/share"),
+ Is.EqualTo(@"\\?\UNC\example.com\share"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\example.com\share\file.txt"),
+ Is.EqualTo(@"\\?\UNC\example.com\share\file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"//example.com/share/file.txt"),
+ Is.EqualTo(@"\\?\UNC\example.com\share\file.txt"));
+
+ // Fully qualified paths with no relative components, but with problematic names, with both kinds of slashes
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:\temp."),
+ Is.EqualTo(@"\\?\C:\temp."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:/temp."),
+ Is.EqualTo(@"\\?\C:\temp."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:\temp.\file.txt"),
+ Is.EqualTo(@"\\?\C:\temp.\file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:/temp./file.txt"),
+ Is.EqualTo(@"\\?\C:\temp.\file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:\temp.\file.txt."),
+ Is.EqualTo(@"\\?\C:\temp.\file.txt."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:/temp./file.txt."),
+ Is.EqualTo(@"\\?\C:\temp.\file.txt."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:\temp "),
+ Is.EqualTo(@"\\?\C:\temp "));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:/temp "),
+ Is.EqualTo(@"\\?\C:\temp "));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:\temp\file.txt "),
+ Is.EqualTo(@"\\?\C:\temp\file.txt "));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"C:/temp/file.txt "),
+ Is.EqualTo(@"\\?\C:\temp\file.txt "));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\example.com\share."),
+ Is.EqualTo(@"\\?\UNC\example.com\share."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"//example.com/share."),
+ Is.EqualTo(@"\\?\UNC\example.com\share."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\example.com\share\file.txt."),
+ Is.EqualTo(@"\\?\UNC\example.com\share\file.txt."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"//example.com/share./file.txt."),
+ Is.EqualTo(@"\\?\UNC\example.com\share.\file.txt."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\example.com\share "),
+ Is.EqualTo(@"\\?\UNC\example.com\share "));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"//example.com/share "),
+ Is.EqualTo(@"\\?\UNC\example.com\share "));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\example.com\share\file.txt "),
+ Is.EqualTo(@"\\?\UNC\example.com\share\file.txt "));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"//example.com/share/file.txt "),
+ Is.EqualTo(@"\\?\UNC\example.com\share\file.txt "));
+
+ // Normalization disabled for paths with @"\\?\" prefix
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\C:\"),
+ Is.EqualTo(@"\\?\C:\"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\C:\temp"),
+ Is.EqualTo(@"\\?\C:\temp"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\C:\temp\file.txt"),
+ Is.EqualTo(@"\\?\C:\temp\file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\C:\temp."),
+ Is.EqualTo(@"\\?\C:\temp."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\C:\temp.\file.txt"),
+ Is.EqualTo(@"\\?\C:\temp.\file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\C:\temp.\file.txt."),
+ Is.EqualTo(@"\\?\C:\temp.\file.txt."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\C:\temp "),
+ Is.EqualTo(@"\\?\C:\temp "));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\C:\temp\file.txt "),
+ Is.EqualTo(@"\\?\C:\temp\file.txt "));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\C:\"),
+ Is.EqualTo(@"\\?\C:\"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\UNC\example.com\share"),
+ Is.EqualTo(@"\\?\UNC\example.com\share"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\UNC\example.com\share\file.txt"),
+ Is.EqualTo(@"\\?\UNC\example.com\share\file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\UNC\example.com\share."),
+ Is.EqualTo(@"\\?\UNC\example.com\share."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\UNC\example.com\share.\file.txt"),
+ Is.EqualTo(@"\\?\UNC\example.com\share.\file.txt"));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\UNC\example.com\share.\file.txt."),
+ Is.EqualTo(@"\\?\UNC\example.com\share.\file.txt."));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\UNC\example.com\share "),
+ Is.EqualTo(@"\\?\UNC\example.com\share "));
+ Assert.That(
+ SystemIOWindows.PrefixWithUNC(@"\\?\UNC\example.com\share\file.txt "),
+ Is.EqualTo(@"\\?\UNC\example.com\share\file.txt "));
+ }
+
+ [Test]
+ public void TestPathGetFullPathInWindowsClient()
+ {
+ if (!Platform.IsClientWindows)
+ {
+ return;
+ }
+
+ // Normalization of basic relative paths, with both kinds of slashes
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@".")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"temp"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"temp")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"temp\file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"temp\file.txt")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"/"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"/")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\temp"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\temp")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"/temp"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"/temp")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"/temp/file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"/temp/file.txt")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"/temp/file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"/temp/file.txt")));
+
+ // Normalization of full qualified paths, but with relative components, with both kinds of slashes
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:\temp\."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:\temp\.")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:/temp/."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:/temp/.")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:\temp\.."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:\temp\..")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:/temp/.."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:/temp/..")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:\temp\..\folder"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:\temp\..\folder")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:/temp/../folder"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:/temp/../folder")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:\temp\.\file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:\temp\.\file.txt")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:/temp/./file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:/temp/./file.txt")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\example.com\share\."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\example.com\share\.")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"//example.com/share/."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"//example.com/share/.")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\example.com\share\.."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\example.com\share\..")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"//example.com/share/.."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"//example.com/share/..")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\example.com\share\..\folder"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\example.com\share\..\folder")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"//example.com/share/../folder"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"//example.com/share/../folder")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\example.com\share\.\file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\example.com\share\.\file.txt")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"//example.com/share/./file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"//example.com/share/./file.txt")));
+
+ // Fully qualified paths with no relative components, with both kinds of slashes
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:\"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:\")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:/"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:/")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:\temp"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:\temp")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:/temp"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:/temp")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:\temp\file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:\temp\file.txt")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:/temp/file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"C:/temp/file.txt")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\example.com\share"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\example.com\share")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"//example.com/share"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"//example.com/share")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\example.com\share\file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\example.com\share\file.txt")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"//example.com/share/file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"//example.com/share/file.txt")));
+
+ // Fully qualified paths with no relative components, but with problematic names, with both kinds of slashes
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:\temp."),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"C:\temp.")).And.EqualTo(@"C:\temp."));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:/temp."),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"C:/temp.")).And.EqualTo(@"C:\temp."));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:\temp.\file.txt"),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"C:\temp.\file.txt")).And.EqualTo(@"C:\temp.\file.txt"));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:/temp./file.txt"),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"C:/temp./file.txt")).And.EqualTo(@"C:\temp.\file.txt"));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:\temp.\file.txt."),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"C:\temp.\file.txt.")).And.EqualTo(@"C:\temp.\file.txt."));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:/temp./file.txt."),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"C:/temp./file.txt.")).And.EqualTo(@"C:\temp.\file.txt."));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:\temp "),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"C:\temp ")).And.EqualTo(@"C:\temp "));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:/temp "),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"C:/temp ")).And.EqualTo(@"C:\temp "));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:\temp\file.txt "),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"C:\temp\file.txt ")).And.EqualTo(@"C:\temp\file.txt "));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"C:/temp/file.txt "),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"C:/temp/file.txt ")).And.EqualTo(@"C:\temp\file.txt "));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\example.com\share."),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"\\example.com\share.")).And.EqualTo(@"\\example.com\share."));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"//example.com/share."),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"//example.com/share.")).And.EqualTo(@"\\example.com\share."));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\example.com\share\file.txt."),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"\\example.com\share\file.txt.")).And.EqualTo(@"\\example.com\share\file.txt."));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"//example.com/share./file.txt."),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"//example.com/share./file.txt.")).And.EqualTo(@"\\example.com\share.\file.txt."));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\example.com\share "),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"\\example.com\share ")).And.EqualTo(@"\\example.com\share "));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"//example.com/share "),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"//example.com/share ")).And.EqualTo(@"\\example.com\share "));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\example.com\share\file.txt "),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"\\example.com\share\file.txt ")).And.EqualTo(@"\\example.com\share\file.txt "));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"//example.com/share/file.txt "),
+ Is.Not.EqualTo(System.IO.Path.GetFullPath(@"//example.com/share/file.txt ")).And.EqualTo(@"\\example.com\share\file.txt "));
+
+ // Normalization disabled for paths with @"\\?\" prefix
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\C:\"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\C:\")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\C:\temp"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\C:\temp")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\C:\temp\file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\C:\temp\file.txt")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\C:\temp."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\C:\temp.")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\C:\temp.\file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\C:\temp.\file.txt")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\C:\temp.\file.txt."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\C:\temp.\file.txt.")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\C:\temp "),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\C:\temp ")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\C:\temp\file.txt "),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\C:\temp\file.txt ")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\C:\"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\C:\")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\UNC\example.com\share"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\UNC\example.com\share")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\UNC\example.com\share\file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\UNC\example.com\share\file.txt")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\UNC\example.com\share."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\UNC\example.com\share.")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\UNC\example.com\share.\file.txt"),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\UNC\example.com\share.\file.txt")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\UNC\example.com\share.\file.txt."),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\UNC\example.com\share.\file.txt.")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\UNC\example.com\share "),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\UNC\example.com\share ")));
+ Assert.That(
+ SystemIO.IO_WIN.PathGetFullPath(@"\\?\UNC\example.com\share\file.txt "),
+ Is.EqualTo(System.IO.Path.GetFullPath(@"\\?\UNC\example.com\share\file.txt ")));
+ }
}
}