mirror of
https://github.com/duplicati/duplicati.git
synced 2025-11-28 03:20:25 +08:00
153 lines
8.2 KiB
C#
Executable file
153 lines
8.2 KiB
C#
Executable file
// Copyright (C) 2025, The Duplicati Team
|
|
// https://duplicati.com, hello@duplicati.com
|
|
//
|
|
// 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.
|
|
// 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;
|
|
}
|
|
}
|
|
}
|