Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable getting and deleting symbolic links #1161

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/Renci.SshNet/ISftpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,18 @@ public interface ISftpClient : IBaseClient, IDisposable
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
void DeleteFile(string path);

/// <summary>
/// Deletes remote symbolic link specified by path.
/// </summary>
/// <param name="path">File to be deleted path.</param>
/// <exception cref="ArgumentException"><paramref name="path"/> is <b>null</b> or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to delete the file was denied by the remote host. <para>-or-</para> A SSH command was denied by the server.</exception>
/// <exception cref="SshException">A SSH error where <see cref="Exception.Message"/> is the message from the remote host.</exception>
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
void DeleteSymbolicLink(string path);

/// <summary>
/// Asynchronously deletes remote file specified by path.
/// </summary>
Expand Down Expand Up @@ -580,6 +592,20 @@ public interface ISftpClient : IBaseClient, IDisposable
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
bool Exists(string path);

/// <summary>
/// Checks whether symbolic link exists;
/// </summary>
/// <param name="path">The path.</param>
/// <returns>
/// <c>true</c> if directory or file exists; otherwise <c>false</c>.
/// </returns>
/// <exception cref="ArgumentException"><paramref name="path"/> is <b>null</b> or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to perform the operation was denied by the remote host. <para>-or-</para> A SSH command was denied by the server.</exception>
/// <exception cref="SshException">A SSH error where <see cref="Exception.Message"/> is the message from the remote host.</exception>
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
bool SymbolicLinkExists(string path);

/// <summary>
/// Gets reference to remote file or directory.
/// </summary>
Expand All @@ -593,6 +619,19 @@ public interface ISftpClient : IBaseClient, IDisposable
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
ISftpFile Get(string path);

/// <summary>
/// Gets reference to remote symbolic link.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>
/// A reference to <see cref="ISftpFile"/> file object.
/// </returns>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</exception>
/// <exception cref="ArgumentNullException"><paramref name="path" /> is <b>null</b>.</exception>
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
ISftpFile GetSymbolicLink(string path);

/// <summary>
/// Gets the <see cref="SftpFileAttributes"/> of the file on the path.
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion src/Renci.SshNet/Sftp/ISftpSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ internal interface ISftpSession : ISubsystemSession
/// Resolves a given path into an absolute path on the server.
/// </summary>
/// <param name="path">The path to resolve.</param>
/// <param name="getRealPath">Boolean determining whether to get the ral path.</param>
/// <returns>
/// The absolute path.
/// </returns>
string GetCanonicalPath(string path);
string GetCanonicalPath(string path, bool getRealPath = true);

Task<string> GetCanonicalPathAsync(string path, CancellationToken cancellationToken);

Expand Down
10 changes: 8 additions & 2 deletions src/Renci.SshNet/Sftp/SftpSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,20 @@ internal void SendMessage(SftpMessage sftpMessage)
/// Resolves a given path into an absolute path on the server.
/// </summary>
/// <param name="path">The path to resolve.</param>
/// <param name="getRealPath">Boolean determining whether to get the real path.</param>
/// <returns>
/// The absolute path.
/// </returns>
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);
Expand Down Expand Up @@ -152,7 +159,6 @@ public string GetCanonicalPath(string path)
public async Task<string> 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)
Expand Down
85 changes: 85 additions & 0 deletions src/Renci.SshNet/SftpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,38 @@ public ISftpFile Get(string path)
return new SftpFile(_sftpSession, fullPath, attributes);
}

/// <summary>
/// Gets reference to remote symbolic link.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>
/// A reference to <see cref="ISftpFile"/> file object.
/// </returns>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</exception>
/// <exception cref="ArgumentNullException"><paramref name="path" /> is <b>null</b>.</exception>
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
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);
}

/// <summary>
/// Checks whether file or directory exists;
/// </summary>
Expand Down Expand Up @@ -793,6 +825,45 @@ public bool Exists(string path)
}
}

/// <summary>
/// Checks whether symbolic link exists;
/// </summary>
/// <param name="path">The path.</param>
/// <returns>
/// <c>true</c> if directory or file exists; otherwise <c>false</c>.
/// </returns>
/// <exception cref="ArgumentException"><paramref name="path"/> is <b>null</b> or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to perform the operation was denied by the remote host. <para>-or-</para> A SSH command was denied by the server.</exception>
/// <exception cref="SshException">A SSH error where <see cref="Exception.Message"/> is the message from the remote host.</exception>
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
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;
}
}

/// <summary>
/// Downloads remote file specified by the path into the stream.
/// </summary>
Expand Down Expand Up @@ -1479,6 +1550,20 @@ public void Delete(string path)
file.Delete();
}

/// <summary>
/// Deletes the specified symbolic link.
/// </summary>
/// <param name="path">The name of the file or directory to be deleted. Wildcard characters are not supported.</param>
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <b>null</b>.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</exception>
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
public void DeleteSymbolicLink(string path)
{
var symLink = GetSymbolicLink(path);
symLink.Delete();
}

/// <summary>
/// Returns the date and time the specified file or directory was last accessed.
/// </summary>
Expand Down