diff --git a/src/Renci.SshNet/ISftpClient.cs b/src/Renci.SshNet/ISftpClient.cs
index 04525dcbc..b7ca2f4f8 100644
--- a/src/Renci.SshNet/ISftpClient.cs
+++ b/src/Renci.SshNet/ISftpClient.cs
@@ -700,6 +700,21 @@ public interface ISftpClient : IBaseClient
/// The method was called after the client was disposed.
SftpFileAttributes GetAttributes(string path);
+ ///
+ /// Gets the of the file on the path.
+ ///
+ /// The path to the file.
+ /// The to observe.
+ ///
+ /// A that represents the attribute retrieval operation.
+ /// The task result contains the of the file on the path.
+ ///
+ /// is .
+ /// Client is not connected.
+ /// was not found on the remote host.
+ /// The method was called after the client was disposed.
+ Task GetAttributesAsync(string path, CancellationToken cancellationToken);
+
///
/// Returns the date and time the specified file or directory was last accessed.
///
diff --git a/src/Renci.SshNet/SftpClient.cs b/src/Renci.SshNet/SftpClient.cs
index 90a2f0330..e6d4efc16 100644
--- a/src/Renci.SshNet/SftpClient.cs
+++ b/src/Renci.SshNet/SftpClient.cs
@@ -2094,6 +2094,33 @@ public SftpFileAttributes GetAttributes(string path)
return _sftpSession.RequestLStat(fullPath);
}
+ ///
+ /// Gets the of the file on the path.
+ ///
+ /// The path to the file.
+ /// The to observe.
+ ///
+ /// A that represents the attribute retrieval operation.
+ /// The task result contains the of the file on the path.
+ ///
+ /// is .
+ /// Client is not connected.
+ /// was not found on the remote host.
+ /// The method was called after the client was disposed.
+ public async Task GetAttributesAsync(string path, CancellationToken cancellationToken)
+ {
+ CheckDisposed();
+
+ if (_sftpSession is null)
+ {
+ throw new SshConnectionException("Client not connected.");
+ }
+
+ var fullPath = await _sftpSession.GetCanonicalPathAsync(path, cancellationToken).ConfigureAwait(false);
+
+ return await _sftpSession.RequestLStatAsync(fullPath, cancellationToken).ConfigureAwait(false);
+ }
+
///
/// Sets the specified of the file on the specified path.
///
diff --git a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.GetAttributes.cs b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.GetAttributes.cs
new file mode 100644
index 000000000..99c4570a9
--- /dev/null
+++ b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.GetAttributes.cs
@@ -0,0 +1,47 @@
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
+{
+ public partial class SftpClientTest
+ {
+ [TestMethod]
+ [TestCategory("Sftp")]
+ public void Test_Sftp_GetAttributes_Not_Exists()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ sftp.Connect();
+
+ Assert.ThrowsException(() => sftp.GetAttributes("/asdfgh"));
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Sftp")]
+ public void Test_Sftp_GetAttributes_Null()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ sftp.Connect();
+
+ Assert.ThrowsException(() => sftp.GetAttributes(null));
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Sftp")]
+ public void Test_Sftp_GetAttributes_Current()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ sftp.Connect();
+
+ var attributes = sftp.GetAttributes(".");
+
+ Assert.IsNotNull(attributes);
+
+ sftp.Disconnect();
+ }
+ }
+ }
+}
diff --git a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.GetAttributesAsync.cs b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.GetAttributesAsync.cs
new file mode 100644
index 000000000..096519eff
--- /dev/null
+++ b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.GetAttributesAsync.cs
@@ -0,0 +1,59 @@
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
+{
+ ///
+ /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
+ ///
+ public partial class SftpClientTest
+ {
+ [TestMethod]
+ [TestCategory("Sftp")]
+ public async Task Test_Sftp_GetAttributesAsync_Not_Exists()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ var cts = new CancellationTokenSource();
+ cts.CancelAfter(TimeSpan.FromMinutes(1));
+
+ await sftp.ConnectAsync(cts.Token);
+
+ await Assert.ThrowsExceptionAsync(async () => await sftp.GetAttributesAsync("/asdfgh", cts.Token));
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Sftp")]
+ public async Task Test_Sftp_GetAttributesAsync_Null()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ var cts = new CancellationTokenSource();
+ cts.CancelAfter(TimeSpan.FromMinutes(1));
+
+ await sftp.ConnectAsync(cts.Token);
+
+ await Assert.ThrowsExceptionAsync(async () => await sftp.GetAttributesAsync(null, cts.Token));
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Sftp")]
+ public async Task Test_Sftp_GetAttributesAsync_Current()
+ {
+ using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+ {
+ var cts = new CancellationTokenSource();
+ cts.CancelAfter(TimeSpan.FromMinutes(1));
+
+ await sftp.ConnectAsync(cts.Token);
+
+ var fileAttributes = await sftp.GetAttributesAsync(".", cts.Token);
+
+ Assert.IsNotNull(fileAttributes);
+
+ sftp.Disconnect();
+ }
+ }
+ }
+}
diff --git a/test/Renci.SshNet.IntegrationTests/SftpClientTests.cs b/test/Renci.SshNet.IntegrationTests/SftpClientTests.cs
index 67b3b28c1..1b43bfa11 100644
--- a/test/Renci.SshNet.IntegrationTests/SftpClientTests.cs
+++ b/test/Renci.SshNet.IntegrationTests/SftpClientTests.cs
@@ -177,5 +177,33 @@ public async Task Create_file_and_delete_using_DeleteAsync()
Assert.IsFalse(await _sftpClient.ExistsAsync(testFileName).ConfigureAwait(false));
}
+
+ [TestMethod]
+ public void Create_file_and_use_GetAttributes()
+ {
+ var testFileName = "test-file.txt";
+ var testContent = "file content";
+
+ using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
+ _sftpClient.UploadFile(fileStream, testFileName);
+
+ var attributes = _sftpClient.GetAttributes(testFileName);
+ Assert.IsNotNull(attributes);
+ Assert.IsTrue(attributes.IsRegularFile);
+ }
+
+ [TestMethod]
+ public async Task Create_file_and_use_GetAttributesAsync()
+ {
+ var testFileName = "test-file.txt";
+ var testContent = "file content";
+
+ using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
+ await _sftpClient.UploadFileAsync(fileStream, testFileName).ConfigureAwait(false);
+
+ var attributes = await _sftpClient.GetAttributesAsync(testFileName, CancellationToken.None).ConfigureAwait(false);
+ Assert.IsNotNull(attributes);
+ Assert.IsTrue(attributes.IsRegularFile);
+ }
}
}
diff --git a/test/Renci.SshNet.Tests/Classes/SftpClientTest.GetAttributes.cs b/test/Renci.SshNet.Tests/Classes/SftpClientTest.GetAttributes.cs
new file mode 100644
index 000000000..d91ce0d85
--- /dev/null
+++ b/test/Renci.SshNet.Tests/Classes/SftpClientTest.GetAttributes.cs
@@ -0,0 +1,30 @@
+using System;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Properties;
+
+namespace Renci.SshNet.Tests.Classes
+{
+ public partial class SftpClientTest
+ {
+ [TestMethod]
+ public void GetAttributes_Throws_WhenNotConnected()
+ {
+ using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+ {
+ Assert.ThrowsException(() => sftp.GetAttributes("."));
+ }
+ }
+
+ [TestMethod]
+ public void GetAttributes_Throws_WhenDisposed()
+ {
+ var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD);
+ sftp.Dispose();
+
+ Assert.ThrowsException(() => sftp.GetAttributes("."));
+ }
+ }
+}
diff --git a/test/Renci.SshNet.Tests/Classes/SftpClientTest.GetAttributesAsync.cs b/test/Renci.SshNet.Tests/Classes/SftpClientTest.GetAttributesAsync.cs
new file mode 100644
index 000000000..837307c74
--- /dev/null
+++ b/test/Renci.SshNet.Tests/Classes/SftpClientTest.GetAttributesAsync.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Properties;
+
+namespace Renci.SshNet.Tests.Classes
+{
+ public partial class SftpClientTest
+ {
+ [TestMethod]
+ public async Task GetAttributesAsync_Throws_WhenNotConnected()
+ {
+ using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+ {
+ await Assert.ThrowsExceptionAsync(() => sftp.GetAttributesAsync(".", CancellationToken.None));
+ }
+ }
+
+ [TestMethod]
+ public async Task GetAttributesAsync_Throws_WhenDisposed()
+ {
+ var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD);
+ sftp.Dispose();
+
+ await Assert.ThrowsExceptionAsync(() => sftp.GetAttributesAsync(".", CancellationToken.None));
+ }
+ }
+}