Skip to content

2025.1.0

Latest

Choose a tag to compare

@Rob-Hague Rob-Hague released this 27 Oct 20:56
6390ede

Highlights

  • Add DownloadFileAsync and UploadFileAsync to SftpClient (#1634)
  • Much improved performance of SftpFileStream in consecutive read (e.g. SftpFileStream.CopyTo) scenarios (#1705)

Breaking changes:

  • SftpFileStream previously had some incomplete synchronisation for multi-threaded access, but was not advertised nor fully functioning as thread safe. This synchronisation was removed in #1705. When accessing an SftpFileStream instance from multiple threads simultaneously, ensure there exists appropriate synchronisation.
  • SftpClient.CreateText and WriteAll{Bytes/Text/Lines} were changed in #1686 to truncate the file before writing if it exists, to align with the equivalent methods on System.IO.File. Given that the prior behaviour was 14 years old, the change treads the line between breaking change and bug fix.
  • IEnumerable<string> ReadLines on SftpClient was updated in #1681 to download and yield lines during enumeration rather than reading them all up front and returning the result. This means that the connection must be active during enumeration. When storing the result of ReadLines for later use, consider using string[] ReadAllLines instead.

What's Changed

New Contributors

Full Changelog: 2025.0.0...2025.1.0

API diff
  namespace Renci.SshNet
  {
      public class ConnectionInfo
      {
+         public Microsoft.Extensions.Logging.ILoggerFactory? LoggerFactory { get; set; }
      }
      public interface ISftpClient : Renci.SshNet.IBaseClient
      {
+         System.Threading.Tasks.Task DownloadFileAsync(string path, System.IO.Stream output, System.Threading.CancellationToken cancellationToken = default);
+         System.Threading.Tasks.Task<Renci.SshNet.Sftp.SftpFileAttributes> GetAttributesAsync(string path, System.Threading.CancellationToken cancellationToken);
+         System.Threading.Tasks.Task UploadFileAsync(System.IO.Stream input, string path, System.Threading.CancellationToken cancellationToken = default);
      }
      public class SftpClient : Renci.SshNet.BaseClient, Renci.SshNet.ISftpClient, Renci.SshNet.IBaseClient
      {
+         public System.Threading.Tasks.Task DownloadFileAsync(string path, System.IO.Stream output, System.Threading.CancellationToken cancellationToken = default);
+         public System.Threading.Tasks.Task<Renci.SshNet.Sftp.SftpFileAttributes> GetAttributesAsync(string path, System.Threading.CancellationToken cancellationToken);
+         public System.Threading.Tasks.Task UploadFileAsync(System.IO.Stream input, string path, System.Threading.CancellationToken cancellationToken = default);
      }
      public sealed class ShellStream
      {
+         public void ChangeWindowSize(uint columns, uint rows, uint width, uint height);
      }
  }
  namespace Renci.SshNet.Common
  {
      public class SftpPathNotFoundException : Renci.SshNet.Common.SftpException
      {
+         public SftpPathNotFoundException(string? message, string? path, System.Exception? innerException);
+         public SftpPathNotFoundException(string? message, string? path);
+         public string? Path { get; }
      }
+     public class SftpException : Renci.SshNet.Common.SshException
+     {
+         public SftpException(Renci.SshNet.Sftp.StatusCode statusCode, string? message, System.Exception? innerException);
+         public SftpException(Renci.SshNet.Sftp.StatusCode statusCode, string? message);
+         public SftpException(Renci.SshNet.Sftp.StatusCode statusCode);
+         public Renci.SshNet.Sftp.StatusCode StatusCode { get; }
+     }
  }
  namespace Renci.SshNet.Security
  {
+     public class KeyExchangeDiffieHellman : Renci.SshNet.Security.KeyExchange
+     {
+         public KeyExchangeDiffieHellman(string name, Org.BouncyCastle.Crypto.Parameters.DHParameters parameters, System.Security.Cryptography.HashAlgorithmName hashAlgorithm);
+         public override void Start(Renci.SshNet.Session session, Renci.SshNet.Messages.Transport.KeyExchangeInitMessage message, bool sendClientInitMessage);
+         public override string Name { get; }
+     }
+     public class KeyExchangeDiffieHellmanGroupExchange : Renci.SshNet.Security.KeyExchange
+     {
+         public KeyExchangeDiffieHellmanGroupExchange(string name, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, uint minimumGroupSize, uint preferredGroupSize, uint maximumGroupSize);
+         public KeyExchangeDiffieHellmanGroupExchange(string name, System.Security.Cryptography.HashAlgorithmName hashAlgorithm);
+         public override void Start(Renci.SshNet.Session session, Renci.SshNet.Messages.Transport.KeyExchangeInitMessage message, bool sendClientInitMessage);
+         public override string Name { get; }
+     }
  }
  namespace Renci.SshNet.Sftp
  {
      public sealed class SftpFileAttributes
      {
+         public bool IsGroupIDBitSet { get; set; }
+         public bool IsStickyBitSet { get; set; }
+         public bool IsUIDBitSet { get; set; }
      }

+     public enum StatusCode
+     {
+         Ok = 0,
+         Eof = 1,
+         NoSuchFile = 2,
+         PermissionDenied = 3,
+         Failure = 4,
+         BadMessage = 5,
+         NoConnection = 6,
+         ConnectionLost = 7,
+         OperationUnsupported = 8,
+     }
  }