diff --git a/src/Renci.SshNet/ISftpClient.cs b/src/Renci.SshNet/ISftpClient.cs
index a87a6a713..7122ccdff 100644
--- a/src/Renci.SshNet/ISftpClient.cs
+++ b/src/Renci.SshNet/ISftpClient.cs
@@ -358,6 +358,20 @@ public interface ISftpClient : IBaseClient
/// The method was called after the client was disposed.
void ChangeDirectory(string path);
+ ///
+ /// Asynchronously requests to change the current working directory to the specified path.
+ ///
+ /// The new working directory.
+ /// The token to monitor for cancellation requests.
+ /// A that tracks the asynchronous change working directory request.
+ /// is .
+ /// Client is not connected.
+ /// Permission to change directory denied by remote host. -or- A SSH command was denied by the server.
+ /// was not found on the remote host.
+ /// A SSH error where is the message from the remote host.
+ /// The method was called after the client was disposed.
+ Task ChangeDirectoryAsync(string path, CancellationToken cancellationToken = default);
+
///
/// Changes permissions of file(s) to specified mode.
///
diff --git a/src/Renci.SshNet/Sftp/ISftpSession.cs b/src/Renci.SshNet/Sftp/ISftpSession.cs
index ec7e77802..1c028efdb 100644
--- a/src/Renci.SshNet/Sftp/ISftpSession.cs
+++ b/src/Renci.SshNet/Sftp/ISftpSession.cs
@@ -34,6 +34,14 @@ internal interface ISftpSession : ISubsystemSession
/// The new working directory.
void ChangeDirectory(string path);
+ ///
+ /// Asynchronously requests to change the current working directory to the specified path.
+ ///
+ /// The new working directory.
+ /// The token to monitor for cancellation requests.
+ /// A that tracks the asynchronous change working directory request.
+ Task ChangeDirectoryAsync(string path, CancellationToken cancellationToken = default);
+
///
/// Resolves a given path into an absolute path on the server.
///
diff --git a/src/Renci.SshNet/Sftp/SftpSession.cs b/src/Renci.SshNet/Sftp/SftpSession.cs
index a397d66ba..5b70a7f1f 100644
--- a/src/Renci.SshNet/Sftp/SftpSession.cs
+++ b/src/Renci.SshNet/Sftp/SftpSession.cs
@@ -82,6 +82,24 @@ public void ChangeDirectory(string path)
WorkingDirectory = fullPath;
}
+ ///
+ /// Asynchronously requests to change the current working directory to the specified path.
+ ///
+ /// The new working directory.
+ /// The token to monitor for cancellation requests.
+ /// A that tracks the asynchronous change working directory request.
+ public async Task ChangeDirectoryAsync(string path, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var fullPath = await GetCanonicalPathAsync(path, cancellationToken).ConfigureAwait(false);
+ var handle = await RequestOpenDirAsync(fullPath, cancellationToken).ConfigureAwait(false);
+
+ await RequestCloseAsync(handle, cancellationToken).ConfigureAwait(false);
+
+ WorkingDirectory = fullPath;
+ }
+
internal void SendMessage(SftpMessage sftpMessage)
{
var data = sftpMessage.GetBytes();
diff --git a/src/Renci.SshNet/SftpClient.cs b/src/Renci.SshNet/SftpClient.cs
index 59fe2377e..d36b96c00 100644
--- a/src/Renci.SshNet/SftpClient.cs
+++ b/src/Renci.SshNet/SftpClient.cs
@@ -305,6 +305,33 @@ public void ChangeDirectory(string path)
_sftpSession.ChangeDirectory(path);
}
+ ///
+ /// Asynchronously requests to change the current working directory to the specified path.
+ ///
+ /// The new working directory.
+ /// The token to monitor for cancellation requests.
+ /// A that tracks the asynchronous change working directory request.
+ /// is .
+ /// Client is not connected.
+ /// Permission to change directory denied by remote host. -or- A SSH command was denied by the server.
+ /// was not found on the remote host.
+ /// A SSH error where is the message from the remote host.
+ /// The method was called after the client was disposed.
+ public Task ChangeDirectoryAsync(string path, CancellationToken cancellationToken = default)
+ {
+ CheckDisposed();
+ ThrowHelper.ThrowIfNull(path);
+
+ if (_sftpSession is null)
+ {
+ throw new SshConnectionException("Client not connected.");
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ return _sftpSession.ChangeDirectoryAsync(path, cancellationToken);
+ }
+
///
/// Changes permissions of file(s) to specified mode.
///
diff --git a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs
index 6fb518506..4076c6b01 100644
--- a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs
+++ b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs
@@ -19,6 +19,18 @@ public void Test_Sftp_ChangeDirectory_Root_Dont_Exists()
}
}
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [ExpectedException(typeof(SftpPathNotFoundException))]
+ public async Task Test_Sftp_ChangeDirectory_Root_Dont_ExistsAsync()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
+ await sftp.ChangeDirectoryAsync("/asdasd", CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+
[TestMethod]
[TestCategory("Sftp")]
[ExpectedException(typeof(SftpPathNotFoundException))]
@@ -31,6 +43,18 @@ public void Test_Sftp_ChangeDirectory_Root_With_Slash_Dont_Exists()
}
}
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [ExpectedException(typeof(SftpPathNotFoundException))]
+ public async Task Test_Sftp_ChangeDirectory_Root_With_Slash_Dont_ExistsAsync()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
+ await sftp.ChangeDirectoryAsync("/asdasd/", CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+
[TestMethod]
[TestCategory("Sftp")]
[ExpectedException(typeof(SftpPathNotFoundException))]
@@ -43,6 +67,18 @@ public void Test_Sftp_ChangeDirectory_Subfolder_Dont_Exists()
}
}
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [ExpectedException(typeof(SftpPathNotFoundException))]
+ public async Task Test_Sftp_ChangeDirectory_Subfolder_Dont_ExistsAsync()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
+ await sftp.ChangeDirectoryAsync("/asdasd/sssddds", CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+
[TestMethod]
[TestCategory("Sftp")]
[ExpectedException(typeof(SftpPathNotFoundException))]
@@ -55,6 +91,18 @@ public void Test_Sftp_ChangeDirectory_Subfolder_With_Slash_Dont_Exists()
}
}
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [ExpectedException(typeof(SftpPathNotFoundException))]
+ public async Task Test_Sftp_ChangeDirectory_Subfolder_With_Slash_Dont_ExistsAsync()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
+ await sftp.ChangeDirectoryAsync("/asdasd/sssddds/", CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_ChangeDirectory_Which_Exists()
@@ -67,6 +115,18 @@ public void Test_Sftp_ChangeDirectory_Which_Exists()
}
}
+ [TestMethod]
+ [TestCategory("Sftp")]
+ public async Task Test_Sftp_ChangeDirectory_Which_ExistsAsync()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
+ await sftp.ChangeDirectoryAsync("/usr", CancellationToken.None).ConfigureAwait(false);
+ Assert.AreEqual("/usr", sftp.WorkingDirectory);
+ }
+ }
+
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_ChangeDirectory_Which_Exists_With_Slash()
@@ -78,5 +138,17 @@ public void Test_Sftp_ChangeDirectory_Which_Exists_With_Slash()
Assert.AreEqual("/usr", sftp.WorkingDirectory);
}
}
+
+ [TestMethod]
+ [TestCategory("Sftp")]
+ public async Task Test_Sftp_ChangeDirectory_Which_Exists_With_SlashAsync()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
+ await sftp.ChangeDirectoryAsync("/usr/", CancellationToken.None).ConfigureAwait(false);
+ Assert.AreEqual("/usr", sftp.WorkingDirectory);
+ }
+ }
}
}
diff --git a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs
index 86e02b2ca..59fefb480 100644
--- a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs
+++ b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs
@@ -229,6 +229,75 @@ public void Test_Sftp_Change_Directory()
RemoveAllFiles();
}
+ [TestMethod]
+ [TestCategory("Sftp")]
+ public async Task Test_Sftp_Change_DirectoryAsync()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
+
+ Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet");
+
+ sftp.CreateDirectory("test1");
+
+ await sftp.ChangeDirectoryAsync("test1", CancellationToken.None).ConfigureAwait(false);
+
+ Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1");
+
+ sftp.CreateDirectory("test1_1");
+ sftp.CreateDirectory("test1_2");
+ sftp.CreateDirectory("test1_3");
+
+ var files = sftp.ListDirectory(".");
+
+ Assert.IsTrue(files.First().FullName.StartsWith(string.Format("{0}", sftp.WorkingDirectory)));
+
+ await sftp.ChangeDirectoryAsync("test1_1", CancellationToken.None).ConfigureAwait(false);
+
+ Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1");
+
+ await sftp.ChangeDirectoryAsync("../test1_2", CancellationToken.None).ConfigureAwait(false);
+
+ Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_2");
+
+ await sftp.ChangeDirectoryAsync("..", CancellationToken.None).ConfigureAwait(false);
+
+ Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1");
+
+ await sftp.ChangeDirectoryAsync("..", CancellationToken.None).ConfigureAwait(false);
+
+ Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet");
+
+ files = sftp.ListDirectory("test1/test1_1");
+
+ Assert.IsTrue(files.First().FullName.StartsWith(string.Format("{0}/test1/test1_1", sftp.WorkingDirectory)));
+
+ await sftp.ChangeDirectoryAsync("test1/test1_1", CancellationToken.None).ConfigureAwait(false);
+
+ Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1");
+
+ await sftp.ChangeDirectoryAsync("/home/sshnet/test1/test1_1", CancellationToken.None).ConfigureAwait(false);
+
+ Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1");
+
+ await sftp.ChangeDirectoryAsync("/home/sshnet/test1/test1_1/../test1_2", CancellationToken.None).ConfigureAwait(false);
+
+ Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_2");
+
+ await sftp.ChangeDirectoryAsync("../../", CancellationToken.None).ConfigureAwait(false);
+
+ sftp.DeleteDirectory("test1/test1_1");
+ sftp.DeleteDirectory("test1/test1_2");
+ sftp.DeleteDirectory("test1/test1_3");
+ sftp.DeleteDirectory("test1");
+
+ sftp.Disconnect();
+ }
+
+ RemoveAllFiles();
+ }
+
[TestMethod]
[TestCategory("Sftp")]
[Description("Test passing null to ChangeDirectory.")]
@@ -245,6 +314,22 @@ public void Test_Sftp_ChangeDirectory_Null()
}
}
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [Description("Test passing null to ChangeDirectory.")]
+ [ExpectedException(typeof(ArgumentNullException))]
+ public async Task Test_Sftp_ChangeDirectory_NullAsync()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
+
+ await sftp.ChangeDirectoryAsync(null, CancellationToken.None).ConfigureAwait(false);
+
+ sftp.Disconnect();
+ }
+ }
+
[TestMethod]
[TestCategory("Sftp")]
[Description("Test calling EndListDirectory method more then once.")]
diff --git a/test/Renci.SshNet.IntegrationTests/SftpTests.cs b/test/Renci.SshNet.IntegrationTests/SftpTests.cs
index 81e1217e1..f03ee6a2c 100644
--- a/test/Renci.SshNet.IntegrationTests/SftpTests.cs
+++ b/test/Renci.SshNet.IntegrationTests/SftpTests.cs
@@ -995,7 +995,6 @@ public void Sftp_AppendText_Encoding_ExistingFile()
{
client.DeleteFile(remoteFile);
}
-
}
}
}
@@ -3993,6 +3992,36 @@ public void Sftp_ChangeDirectory_DirectoryDoesNotExist()
}
}
+ [TestMethod]
+ public async Task Sftp_ChangeDirectory_DirectoryDoesNotExistAsync()
+ {
+ const string remoteDirectory = "/home/sshnet/test123";
+
+ using (var client = new SshClient(_connectionInfoFactory.Create()))
+ {
+ await client.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
+
+ using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory)))
+ {
+ await command.ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+
+ using (var client = new SftpClient(_connectionInfoFactory.Create()))
+ {
+ client.Connect();
+
+ try
+ {
+ await client.ChangeDirectoryAsync(remoteDirectory, CancellationToken.None).ConfigureAwait(false);
+ Assert.Fail();
+ }
+ catch (SftpPathNotFoundException)
+ {
+ }
+ }
+ }
+
[TestMethod]
public void Sftp_ChangeDirectory_DirectoryExists()
{
@@ -4052,6 +4081,65 @@ public void Sftp_ChangeDirectory_DirectoryExists()
}
}
+ [TestMethod]
+ public async Task Sftp_ChangeDirectory_DirectoryExistsAsync()
+ {
+ const string remoteDirectory = "/home/sshnet/test123";
+
+ using (var client = new SshClient(_connectionInfoFactory.Create()))
+ {
+ await client.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
+
+ using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory)))
+ {
+ await command.ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ using (var command = client.CreateCommand("mkdir -p " + _remotePathTransformation.Transform(remoteDirectory)))
+ {
+ await command.ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+
+ try
+ {
+ using (var client = new SftpClient(_connectionInfoFactory.Create()))
+ {
+ await client.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
+
+ await client.ChangeDirectoryAsync(remoteDirectory, CancellationToken.None).ConfigureAwait(false);
+
+ Assert.AreEqual(remoteDirectory, client.WorkingDirectory);
+
+ using (var uploadStream = CreateMemoryStream(100))
+ {
+ uploadStream.Position = 0;
+
+ client.UploadFile(uploadStream, "gert.txt");
+
+ uploadStream.Position = 0;
+
+ using (var downloadStream = client.OpenRead(remoteDirectory + "/gert.txt"))
+ {
+ Assert.AreEqual(CreateHash(uploadStream), CreateHash(downloadStream));
+ }
+ }
+ }
+ }
+ finally
+ {
+ using (var client = new SshClient(_connectionInfoFactory.Create()))
+ {
+ await client.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
+
+ using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory)))
+ {
+ await command.ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+ }
+ }
+
[TestMethod]
public void Sftp_DownloadFile_MemoryStream()
{