From de4b7770538314ea063e576194ba8143e33bc187 Mon Sep 17 00:00:00 2001 From: Fernando Bedoya Ortecho Date: Wed, 23 Aug 2023 16:16:06 -0700 Subject: [PATCH 1/2] Add getRealPath parameter to GetCanonized function --- src/Renci.SshNet/Sftp/ISftpSession.cs | 3 ++- src/Renci.SshNet/Sftp/SftpSession.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Renci.SshNet/Sftp/ISftpSession.cs b/src/Renci.SshNet/Sftp/ISftpSession.cs index 39b48e7f1..c70bdbadb 100644 --- a/src/Renci.SshNet/Sftp/ISftpSession.cs +++ b/src/Renci.SshNet/Sftp/ISftpSession.cs @@ -35,10 +35,11 @@ internal interface ISftpSession : ISubsystemSession /// Resolves a given path into an absolute path on the server. /// /// The path to resolve. + /// Boolean determining whether to get the ral path. /// /// The absolute path. /// - string GetCanonicalPath(string path); + string GetCanonicalPath(string path, bool getRealPath = true); Task GetCanonicalPathAsync(string path, CancellationToken cancellationToken); diff --git a/src/Renci.SshNet/Sftp/SftpSession.cs b/src/Renci.SshNet/Sftp/SftpSession.cs index d797378d3..54b0c4ad4 100644 --- a/src/Renci.SshNet/Sftp/SftpSession.cs +++ b/src/Renci.SshNet/Sftp/SftpSession.cs @@ -89,13 +89,20 @@ internal void SendMessage(SftpMessage sftpMessage) /// Resolves a given path into an absolute path on the server. /// /// The path to resolve. + /// Boolean determining whether to get the real path. /// /// The absolute path. /// - public string GetCanonicalPath(string path) + public string GetCanonicalPath(string path, bool getRealPath = true) { var fullPath = GetFullRemotePath(path); + if (!getRealPath) + { + // getRealPath set to false allows us to get a reference to the symbolic link itself and not to the file it points to. + return fullPath; + } + var canonizedPath = string.Empty; var realPathFiles = RequestRealPath(fullPath, nullOnError: true); From 8c7202c8e01405b90b4353df979b890a30ed152f Mon Sep 17 00:00:00 2001 From: Fernando Bedoya Ortecho Date: Wed, 23 Aug 2023 17:05:32 -0700 Subject: [PATCH 2/2] Enable get and delete symlinks --- src/Renci.SshNet/ISftpClient.cs | 39 +++++++++++++ src/Renci.SshNet/Sftp/SftpSession.cs | 1 - src/Renci.SshNet/SftpClient.cs | 85 ++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) diff --git a/src/Renci.SshNet/ISftpClient.cs b/src/Renci.SshNet/ISftpClient.cs index 82117295c..243cd5dc9 100644 --- a/src/Renci.SshNet/ISftpClient.cs +++ b/src/Renci.SshNet/ISftpClient.cs @@ -491,6 +491,18 @@ public interface ISftpClient : IBaseClient, IDisposable /// The method was called after the client was disposed. void DeleteFile(string path); + /// + /// Deletes remote symbolic link specified by path. + /// + /// File to be deleted path. + /// is null or contains only whitespace characters. + /// Client is not connected. + /// was not found on the remote host. + /// Permission to delete the file was denied by the remote host. -or- A SSH command was denied by the server. + /// A SSH error where is the message from the remote host. + /// The method was called after the client was disposed. + void DeleteSymbolicLink(string path); + /// /// Asynchronously deletes remote file specified by path. /// @@ -580,6 +592,20 @@ public interface ISftpClient : IBaseClient, IDisposable /// The method was called after the client was disposed. bool Exists(string path); + /// + /// Checks whether symbolic link exists; + /// + /// The path. + /// + /// true if directory or file exists; otherwise false. + /// + /// is null or contains only whitespace characters. + /// Client is not connected. + /// Permission to perform the operation was denied by the remote host. -or- A SSH command was denied by the server. + /// A SSH error where is the message from the remote host. + /// The method was called after the client was disposed. + bool SymbolicLinkExists(string path); + /// /// Gets reference to remote file or directory. /// @@ -593,6 +619,19 @@ public interface ISftpClient : IBaseClient, IDisposable /// The method was called after the client was disposed. ISftpFile Get(string path); + /// + /// Gets reference to remote symbolic link. + /// + /// The path. + /// + /// A reference to file object. + /// + /// Client is not connected. + /// was not found on the remote host. + /// is null. + /// The method was called after the client was disposed. + ISftpFile GetSymbolicLink(string path); + /// /// Gets the of the file on the path. /// diff --git a/src/Renci.SshNet/Sftp/SftpSession.cs b/src/Renci.SshNet/Sftp/SftpSession.cs index 54b0c4ad4..318a5b600 100644 --- a/src/Renci.SshNet/Sftp/SftpSession.cs +++ b/src/Renci.SshNet/Sftp/SftpSession.cs @@ -159,7 +159,6 @@ public string GetCanonicalPath(string path, bool getRealPath = true) public async Task GetCanonicalPathAsync(string path, CancellationToken cancellationToken) { var fullPath = GetFullRemotePath(path); - var canonizedPath = string.Empty; var realPathFiles = await RequestRealPathAsync(fullPath, nullOnError: true, cancellationToken).ConfigureAwait(false); if (realPathFiles != null) diff --git a/src/Renci.SshNet/SftpClient.cs b/src/Renci.SshNet/SftpClient.cs index a275413a1..afb5ddc4c 100644 --- a/src/Renci.SshNet/SftpClient.cs +++ b/src/Renci.SshNet/SftpClient.cs @@ -737,6 +737,38 @@ public ISftpFile Get(string path) return new SftpFile(_sftpSession, fullPath, attributes); } + /// + /// Gets reference to remote symbolic link. + /// + /// The path. + /// + /// A reference to file object. + /// + /// Client is not connected. + /// was not found on the remote host. + /// is null. + /// The method was called after the client was disposed. + public ISftpFile GetSymbolicLink(string path) + { + CheckDisposed(); + + if (path is null) + { + throw new ArgumentNullException(nameof(path)); + } + + if (_sftpSession is null) + { + throw new SshConnectionException("Client not connected."); + } + + var fullPath = _sftpSession.GetCanonicalPath(path, false); + + var attributes = _sftpSession.RequestLStat(fullPath); + + return new SftpFile(_sftpSession, fullPath, attributes); + } + /// /// Checks whether file or directory exists; /// @@ -793,6 +825,45 @@ public bool Exists(string path) } } + /// + /// Checks whether symbolic link exists; + /// + /// The path. + /// + /// true if directory or file exists; otherwise false. + /// + /// is null or contains only whitespace characters. + /// Client is not connected. + /// Permission to perform the operation was denied by the remote host. -or- A SSH command was denied by the server. + /// A SSH error where is the message from the remote host. + /// The method was called after the client was disposed. + public bool SymbolicLinkExists(string path) + { + CheckDisposed(); + + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentException("path"); + } + + if (_sftpSession is null) + { + throw new SshConnectionException("Client not connected."); + } + + var fullPath = _sftpSession.GetCanonicalPath(path, false); + + try + { + _ = _sftpSession.RequestLStat(fullPath); + return true; + } + catch (SftpPathNotFoundException) + { + return false; + } + } + /// /// Downloads remote file specified by the path into the stream. /// @@ -1479,6 +1550,20 @@ public void Delete(string path) file.Delete(); } + /// + /// Deletes the specified symbolic link. + /// + /// The name of the file or directory to be deleted. Wildcard characters are not supported. + /// is null. + /// Client is not connected. + /// was not found on the remote host. + /// The method was called after the client was disposed. + public void DeleteSymbolicLink(string path) + { + var symLink = GetSymbolicLink(path); + symLink.Delete(); + } + /// /// Returns the date and time the specified file or directory was last accessed. ///