From a1263cd48ce10cfb6e639633dc00809efef5cfce Mon Sep 17 00:00:00 2001 From: "Marcelo C." Date: Fri, 10 Jan 2025 12:05:40 -0300 Subject: [PATCH] Path normalization Path separators are converted to \ which is Windows & Linux compatible, overwriting the Path.Combine separator (which would be host dependant) --- .../Backend/CIFS/SMBShareConnection.cs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/Duplicati/Library/Backend/CIFS/SMBShareConnection.cs b/Duplicati/Library/Backend/CIFS/SMBShareConnection.cs index ce44882a4..f47b08233 100644 --- a/Duplicati/Library/Backend/CIFS/SMBShareConnection.cs +++ b/Duplicati/Library/Backend/CIFS/SMBShareConnection.cs @@ -102,7 +102,7 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable NTStatus status; object fileHandle; FileStatus fileStatus; - status = _smbFileStore.CreateFile(out fileHandle, out fileStatus, Path.Combine(_connectionParameters.Path, fileName), + status = _smbFileStore.CreateFile(out fileHandle, out fileStatus, NormalizeSlashes(Path.Combine(_connectionParameters.Path, fileName)), AccessMask.GENERIC_WRITE | AccessMask.DELETE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, @@ -145,15 +145,15 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable try { // Normalize path separators to forward slashes and trim any trailing separators - string normalizedPath = path.Replace('\\', '/').TrimEnd('/'); + string linuxNormalizedPath = path.Replace('/', '\\').TrimEnd('\\'); string currentPath = ""; - foreach (string part in normalizedPath.Split('/', StringSplitOptions.RemoveEmptyEntries)) + foreach (string part in linuxNormalizedPath.Split('\\', StringSplitOptions.RemoveEmptyEntries)) { if (string.IsNullOrWhiteSpace(part) || part == ".") continue; - currentPath = currentPath.Length == 0 ? part : $"{currentPath}/{part}"; + currentPath = currentPath.Length == 0 ? part : $"{currentPath}\\{part}"; object? fileHandle = null; try { @@ -208,7 +208,7 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable var status = _smbFileStore.CreateFile( out directoryHandle, out fileStatus, - _connectionParameters.Path, + NormalizeSlashes(_connectionParameters.Path), AccessMask.GENERIC_READ, FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, @@ -220,7 +220,7 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable if (status == NTStatus.STATUS_OBJECT_PATH_NOT_FOUND || status == NTStatus.STATUS_OBJECT_NAME_NOT_FOUND) throw new FolderMissingException(); else - throw new UserInformationException($"{LC.L("Failed to open directory")} {_connectionParameters.Path} with status {status.ToString()}","DirectoryOpenError"); + throw new UserInformationException($"{LC.L("Failed to open directory")} { NormalizeSlashes(_connectionParameters.Path)} with status {status.ToString()}","DirectoryOpenError"); List fileList; status = _smbFileStore.QueryDirectory( @@ -279,7 +279,7 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable object? fileHandle; FileStatus fileStatus; NTStatus status = _smbFileStore.CreateFile(out fileHandle, out fileStatus, - Path.Combine(_connectionParameters.Path, filename), // This is where the file name is concatenated with the path + NormalizeSlashes(Path.Combine(_connectionParameters.Path, filename)), AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); @@ -341,7 +341,7 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable object fileHandle; FileStatus fileStatus; NTStatus status = _smbFileStore.CreateFile(out fileHandle, out fileStatus, - Path.Combine(_connectionParameters.Path, filename), // This is where the file name is concatenated with the path + NormalizeSlashes(Path.Combine(_connectionParameters.Path, filename)), AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_SUPERSEDE, @@ -383,6 +383,18 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable _semaphore.Release(); } } + + /// + /// Normalizes paths to use backslashes (for Windows shares compatibility) and removes trailing slashes. + /// + /// Samba deals with \ and / in paths, but Windows shares require backslashes. + /// + /// Path to be normalized + /// + private string NormalizeSlashes(string path) + { + return path.Replace('/', '\\').TrimEnd('\\'); + } /// /// Synchronously dispose the resources.