diff --git a/src/Renci.SshNet/ISftpClient.cs b/src/Renci.SshNet/ISftpClient.cs index dc2d2c899..6d55b0bfa 100644 --- a/src/Renci.SshNet/ISftpClient.cs +++ b/src/Renci.SshNet/ISftpClient.cs @@ -705,7 +705,6 @@ public interface ISftpClient : IDisposable IEnumerable ListDirectory(string path, Action listCallback = null); #if FEATURE_TAP - /// /// Asynchronously retrieves list of files in remote directory. /// @@ -720,7 +719,29 @@ public interface ISftpClient : IDisposable /// Permission to list the contents of the directory 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. - Task> ListDirectoryAsync(string path, CancellationToken cancellationToken); +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + [Obsolete("Use EnumerateDirectoryAsync()")] + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] +#endif + Task> ListDirectoryAsync(string path, CancellationToken cancellationToken); + +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + /// + /// Asynchronously enumerates the files in remote directory. + /// + /// The path. + /// The to observe. + /// + /// An that represents the asynchronous enumeration operation. + /// The enumeration contains an async stream of for the files in the directory specified by . + /// + /// is null. + /// Client is not connected. + /// Permission to list the contents of the directory 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. + IAsyncEnumerable EnumerateDirectoryAsync(string path, CancellationToken cancellationToken); +#endif #endif /// diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index 740ec7ee1..740fcd9a1 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -5,17 +5,15 @@ false Renci.SshNet ../Renci.SshNet.snk - 6 + 8.0 true - net35;net40;net472;netstandard1.3;netstandard2.0 + net35;net40;net472;netstandard1.3;netstandard2.1 - - + diff --git a/src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs index 38d60966e..484c6eaaf 100644 --- a/src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs +++ b/src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs @@ -39,7 +39,7 @@ public override bool Verify(byte[] input, byte[] signature) // for 521 sig_size is 132 var sig_size = _key.KeyLength == 521 ? 132 : _key.KeyLength / 4; var ssh_data = new SshDataSignature(signature, sig_size); -#if NETSTANDARD2_0 +#if !NETFRAMEWORK return _key.Ecdsa.VerifyData(input, ssh_data.Signature, _key.HashAlgorithm); #else var ecdsa = (ECDsaCng)_key.Ecdsa; @@ -57,7 +57,7 @@ public override bool Verify(byte[] input, byte[] signature) /// public override byte[] Sign(byte[] input) { -#if NETSTANDARD2_0 +#if !NETFRAMEWORK var signed = _key.Ecdsa.SignData(input, _key.HashAlgorithm); #else var ecdsa = (ECDsaCng)_key.Ecdsa; diff --git a/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs b/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs index 58861f020..fafff4328 100644 --- a/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs +++ b/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs @@ -18,7 +18,7 @@ public class EcdsaKey : Key, IDisposable internal const string ECDSA_P384_OID_VALUE = "1.3.132.0.34"; // Also called nistP384 or secP384r1 internal const string ECDSA_P521_OID_VALUE = "1.3.132.0.35"; // Also called nistP521or secP521r1 -#if !NETSTANDARD2_0 +#if NETFRAMEWORK internal enum KeyBlobMagicNumber : int { BCRYPT_ECDSA_PUBLIC_P256_MAGIC = 0x31534345, @@ -57,7 +57,7 @@ public override string ToString() return string.Format("ecdsa-sha2-nistp{0}", KeyLength); } -#if NETSTANDARD2_0 +#if !NETFRAMEWORK /// /// Gets the HashAlgorithm to use /// @@ -144,7 +144,7 @@ public override BigInteger[] Public byte[] curve; byte[] qx; byte[] qy; -#if NETSTANDARD2_0 +#if !NETFRAMEWORK var parameter = Ecdsa.ExportParameters(false); qx = parameter.Q.X; qy = parameter.Q.Y; @@ -278,7 +278,7 @@ public EcdsaKey(byte[] data) private void Import(string curve_oid, byte[] publickey, byte[] privatekey) { -#if NETSTANDARD2_0 +#if !NETFRAMEWORK var curve = ECCurve.CreateFromValue(curve_oid); var parameter = new ECParameters { diff --git a/src/Renci.SshNet/SftpClient.cs b/src/Renci.SshNet/SftpClient.cs index 4ac9d5543..695c0662c 100644 --- a/src/Renci.SshNet/SftpClient.cs +++ b/src/Renci.SshNet/SftpClient.cs @@ -11,6 +11,7 @@ using Renci.SshNet.Common; using Renci.SshNet.Sftp; #if FEATURE_TAP +using System.Runtime.CompilerServices; using System.Threading.Tasks; #endif @@ -537,7 +538,6 @@ public IEnumerable ListDirectory(string path, Action listCallbac } #if FEATURE_TAP - /// /// Asynchronously retrieves list of files in remote directory. /// @@ -552,11 +552,15 @@ public IEnumerable ListDirectory(string path, Action listCallbac /// Permission to list the contents of the directory 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 async Task> ListDirectoryAsync(string path, CancellationToken cancellationToken) +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + [Obsolete("Use EnumerateDirectoryAsync()")] + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] +#endif + public async Task> ListDirectoryAsync(string path, CancellationToken cancellationToken) { base.CheckDisposed(); if (path == null) - throw new ArgumentNullException("path"); + throw new ArgumentNullException(nameof(path)); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); cancellationToken.ThrowIfCancellationRequested(); @@ -594,6 +598,58 @@ public async Task> ListDirectoryAsync(string path, Cancell return result; } +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + /// + /// Asynchronously enumerates the files in remote directory. + /// + /// The path. + /// The to observe. + /// + /// An that represents the asynchronous enumeration operation. + /// The enumeration contains an async stream of for the files in the directory specified by . + /// + /// is null. + /// Client is not connected. + /// Permission to list the contents of the directory 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 async IAsyncEnumerable EnumerateDirectoryAsync(string path, [EnumeratorCancellation]CancellationToken cancellationToken) + { + base.CheckDisposed(); + if (path == null) + throw new ArgumentNullException(nameof(path)); + if (_sftpSession == null) + throw new SshConnectionException("Client not connected."); + + var fullPath = await _sftpSession.GetCanonicalPathAsync(path, cancellationToken).ConfigureAwait(false); + + var handle = await _sftpSession.RequestOpenDirAsync(fullPath, cancellationToken).ConfigureAwait(false); + try + { + var basePath = (fullPath[fullPath.Length - 1] == '/') ? + fullPath : + fullPath + '/'; + + while (true) + { + var files = await _sftpSession.RequestReadDirAsync(handle, cancellationToken).ConfigureAwait(false); + if (files == null) + { + break; + } + + foreach (var file in files) + { + yield return new SftpFile(_sftpSession, basePath + file.Key, file.Value); + } + } + } + finally + { + await _sftpSession.RequestCloseAsync(handle, cancellationToken).ConfigureAwait(false); + } + } +#endif #endif ///