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/Library/Common/IO
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/Library/Common/IO')
-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
3 files changed, 210 insertions, 85 deletions
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)