Highlights
- Add
DownloadFileAsyncandUploadFileAsynctoSftpClient(#1634) - Much improved performance of
SftpFileStreamin consecutive read (e.g.SftpFileStream.CopyTo) scenarios (#1705)
Breaking changes:
SftpFileStreampreviously 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 anSftpFileStreaminstance from multiple threads simultaneously, ensure there exists appropriate synchronisation.SftpClient.CreateTextandWriteAll{Bytes/Text/Lines}were changed in #1686 to truncate the file before writing if it exists, to align with the equivalent methods onSystem.IO.File. Given that the prior behaviour was 14 years old, the change treads the line between breaking change and bug fix.IEnumerable<string> ReadLinesonSftpClientwas 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 ofReadLinesfor later use, consider usingstring[] ReadAllLinesinstead.
What's Changed
- Version 2025.0.0 by @Rob-Hague in #1631
- Add a debug helper for reading traffic with Wireshark by @Rob-Hague in #1627
- Update docs and remove a leftover DSA certificate algorithm by @Rob-Hague in #1632
- Override more methods in PipeStream, ShellStream by @Rob-Hague in #1637
- Read the underlying buffer in SshDataStream by @Rob-Hague in #1638
- Add UploadFileAsync and DownloadFileAsync methods by @sdt-ndelarosa in #1634
- fix IDE0350 warnings with .NET SDK 9.0.300 by @mus65 in #1645
- Re-introduce ChangeWindow method on ShellStream by @Rasmus715 in #1646
- Fix hang in SftpClient.UploadFile upon error by @Rob-Hague in #1643
- Drop netstandard2.1 target by @mus65 in #1647
- Added GetAttributesAsync to SftpClient by @deckertron9000 in #1648
- Bump alpine from 3.21 to 3.22 in /test/Renci.SshNet.IntegrationTests by @dependabot[bot] in #1651
- Bump dependencies by @dependabot[bot] in #1652
- Check host key algorithms before continuing key exchange by @Rob-Hague in #1642
- Use an array buffer for the sftp packet stream by @Rob-Hague in #1649
- Use ArraySegment for channel data by @Rob-Hague in #1650
- scp: add flag to notify for uploading finished when uploaded an empty file by @thegame4craft in #1658
- Fixes spelling errors by @jacobslusser in #1668
- Bump the dependencies group with 5 updates by @dependabot[bot] in #1663
- Refactor logging to allow a loggerfactory per session by @desdesdes in #1673
- Adapt InternalUploadFile for async by @Rob-Hague in #1653
- Use BouncyCastle for Diffie-Hellman key exchange by @Rob-Hague in #1654
- Remove unnecessary SftpFileStream unit tests and dedup Open{Async} by @Rob-Hague in #1680
- Bump Vampire/setup-wsl from 5.0.1 to 6.0.0 by @dependabot[bot] in #1683
- Use ReadExactly in ReadAllBytes and yield in ReadLines by @Rob-Hague in #1681
- Bump the dependencies group with 4 updates by @dependabot[bot] in #1682
- Truncate existing file in CreateText by @Rob-Hague in #1686
- Fix key material extension during key exchange by @Rob-Hague in #1689
- Bump actions/checkout from 4 to 5 by @dependabot[bot] in #1693
- Use BCL Curve25519 for Windows 10+ by @scott-xu in #1702
- Remove calls to Socket.Poll and use SocketShutdown.Both by @Rob-Hague in #1706
- Fix SftpFileAttributes file type detection by @Rob-Hague in #1688
- CI: add Windows Integration Tests for .NET by @mus65 in #1704
- Avoid rounding issues when checking Timeout values (#1700) by @nikolamilekic in #1712
- Build the read-ahead mechanism into SftpFileStream by @Rob-Hague in #1705
- Add SftpException and SftpPathNotFoundException.Path by @Rob-Hague in #1716
- Internal cleanup in SftpSession by @Rob-Hague in #1717
- Override WriteAsync in ShellStream by @Rob-Hague in #1711
- Fix typos in comments: "lenght" → "length" and "occured" → "occurred" by @Copilot in #1719
- Automatically publish tags to NuGet with Trusted Publishing by @mus65 in #1715
- Set version to 2025.1.0 stable by @Rob-Hague in #1722
New Contributors
- @sdt-ndelarosa made their first contribution in #1634
- @Rasmus715 made their first contribution in #1646
- @deckertron9000 made their first contribution in #1648
- @thegame4craft made their first contribution in #1658
- @desdesdes made their first contribution in #1673
- @nikolamilekic made their first contribution in #1712
- @Copilot made their first contribution in #1719
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,
+ }
}