diff --git a/src/Renci.SshNet/Common/NetConfServerException.cs b/src/Renci.SshNet/Common/NetConfServerException.cs index ab70a8346..1dfc56b11 100644 --- a/src/Renci.SshNet/Common/NetConfServerException.cs +++ b/src/Renci.SshNet/Common/NetConfServerException.cs @@ -1,7 +1,5 @@ -using System; -#if NETFRAMEWORK -using System.Runtime.Serialization; -#endif // NETFRAMEWORK +#nullable enable +using System; namespace Renci.SshNet.Common { @@ -10,7 +8,7 @@ namespace Renci.SshNet.Common /// #if NETFRAMEWORK [Serializable] -#endif // NETFRAMEWORK +#endif public class NetConfServerException : SshException { /// @@ -23,8 +21,8 @@ public NetConfServerException() /// /// Initializes a new instance of the class. /// - /// The message. - public NetConfServerException(string message) + /// + public NetConfServerException(string? message) : base(message) { } @@ -32,25 +30,18 @@ public NetConfServerException(string message) /// /// Initializes a new instance of the class. /// - /// The message. - /// The inner exception. - public NetConfServerException(string message, Exception innerException) + /// + public NetConfServerException(string? message, Exception? innerException) : base(message, innerException) { } #if NETFRAMEWORK - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// The parameter is . - /// The class name is or is zero (0). - protected NetConfServerException(SerializationInfo info, StreamingContext context) + /// + protected NetConfServerException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } -#endif // NETFRAMEWORK +#endif } } diff --git a/src/Renci.SshNet/Common/ProxyException.cs b/src/Renci.SshNet/Common/ProxyException.cs index d324c9a3d..a3c86969a 100644 --- a/src/Renci.SshNet/Common/ProxyException.cs +++ b/src/Renci.SshNet/Common/ProxyException.cs @@ -1,7 +1,5 @@ -using System; -#if NETFRAMEWORK -using System.Runtime.Serialization; -#endif // NETFRAMEWORK +#nullable enable +using System; namespace Renci.SshNet.Common { @@ -10,7 +8,7 @@ namespace Renci.SshNet.Common /// #if NETFRAMEWORK [Serializable] -#endif // NETFRAMEWORK +#endif public class ProxyException : SshException { /// @@ -23,8 +21,8 @@ public ProxyException() /// /// Initializes a new instance of the class. /// - /// The message. - public ProxyException(string message) + /// + public ProxyException(string? message) : base(message) { } @@ -32,25 +30,18 @@ public ProxyException(string message) /// /// Initializes a new instance of the class. /// - /// The message. - /// The inner exception. - public ProxyException(string message, Exception innerException) + /// + public ProxyException(string? message, Exception? innerException) : base(message, innerException) { } #if NETFRAMEWORK - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// The parameter is . - /// The class name is or is zero (0). - protected ProxyException(SerializationInfo info, StreamingContext context) + /// + protected ProxyException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } -#endif // NETFRAMEWORK +#endif } } diff --git a/src/Renci.SshNet/Common/ScpException.cs b/src/Renci.SshNet/Common/ScpException.cs index 564e2994b..08bdbc885 100644 --- a/src/Renci.SshNet/Common/ScpException.cs +++ b/src/Renci.SshNet/Common/ScpException.cs @@ -1,16 +1,14 @@ -using System; -#if NETFRAMEWORK -using System.Runtime.Serialization; -#endif // NETFRAMEWORK +#nullable enable +using System; namespace Renci.SshNet.Common { /// - /// The exception that is thrown when SCP error occurred. + /// The exception that is thrown when an SCP error occurs. /// #if NETFRAMEWORK [Serializable] -#endif // NETFRAMEWORK +#endif public class ScpException : SshException { /// @@ -23,8 +21,8 @@ public ScpException() /// /// Initializes a new instance of the class. /// - /// The message. - public ScpException(string message) + /// + public ScpException(string? message) : base(message) { } @@ -32,25 +30,18 @@ public ScpException(string message) /// /// Initializes a new instance of the class. /// - /// The message. - /// The inner exception. - public ScpException(string message, Exception innerException) + /// + public ScpException(string? message, Exception? innerException) : base(message, innerException) { } #if NETFRAMEWORK - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// The parameter is . - /// The class name is or is zero (0). - protected ScpException(SerializationInfo info, StreamingContext context) + /// + protected ScpException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } -#endif // NETFRAMEWORK +#endif } } diff --git a/src/Renci.SshNet/Common/SftpException.cs b/src/Renci.SshNet/Common/SftpException.cs new file mode 100644 index 000000000..839670d81 --- /dev/null +++ b/src/Renci.SshNet/Common/SftpException.cs @@ -0,0 +1,76 @@ +#nullable enable +using System; + +using Renci.SshNet.Sftp; + +namespace Renci.SshNet.Common +{ + /// + /// The exception that is thrown when an error occurs in the SFTP layer. + /// +#if NETFRAMEWORK + [Serializable] +#endif + public class SftpException : SshException + { + /// + /// Gets the status code that is associated with this exception. + /// + public StatusCode StatusCode { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The status code that indicates the error that occurred. + public SftpException(StatusCode statusCode) + : this(statusCode, message: null, innerException: null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The status code that indicates the error that occurred. + /// The error message that explains the reason for the exception. + public SftpException(StatusCode statusCode, string? message) + : this(statusCode, message, innerException: null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The status code that indicates the error that occurred. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public SftpException(StatusCode statusCode, string? message, Exception? innerException) + : base(string.IsNullOrEmpty(message) ? GetDefaultMessage(statusCode) : message, innerException) + { + StatusCode = statusCode; + } + + private protected static string GetDefaultMessage(StatusCode statusCode) + { +#pragma warning disable IDE0072 // Add missing cases + return statusCode switch + { + StatusCode.Ok => "The operation completed successfully.", + StatusCode.NoSuchFile => "A reference was made to a file that does not exist.", + StatusCode.PermissionDenied => "The user does not have sufficient permissions to perform the operation.", + StatusCode.Failure => "An error occurred, but no specific error code exists to describe the failure.", + StatusCode.BadMessage => "A badly formatted packet or SFTP protocol incompatibility was detected.", + StatusCode.OperationUnsupported => "An attempt was made to perform an operation which is not supported.", + _ => statusCode.ToString() + }; +#pragma warning restore IDE0072 // Add missing cases + } + +#if NETFRAMEWORK + /// + protected SftpException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } +#endif + } +} diff --git a/src/Renci.SshNet/Common/SftpPathNotFoundException.cs b/src/Renci.SshNet/Common/SftpPathNotFoundException.cs index ed628dee0..83fd904f0 100644 --- a/src/Renci.SshNet/Common/SftpPathNotFoundException.cs +++ b/src/Renci.SshNet/Common/SftpPathNotFoundException.cs @@ -1,7 +1,7 @@ -using System; -#if NETFRAMEWORK -using System.Runtime.Serialization; -#endif // NETFRAMEWORK +#nullable enable +using System; + +using Renci.SshNet.Sftp; namespace Renci.SshNet.Common { @@ -10,47 +10,82 @@ namespace Renci.SshNet.Common /// #if NETFRAMEWORK [Serializable] -#endif // NETFRAMEWORK - public class SftpPathNotFoundException : SshException +#endif + public class SftpPathNotFoundException : SftpException { + private const StatusCode Code = StatusCode.NoSuchFile; + + /// + /// Gets the path that cannot be found. + /// + /// + /// The path that cannot be found, or if no path was + /// passed to the constructor for this instance. + /// + public string? Path { get; } + /// /// Initializes a new instance of the class. /// public SftpPathNotFoundException() + : this(message: null, path: null, innerException: null) { } /// /// Initializes a new instance of the class. /// - /// The message. - public SftpPathNotFoundException(string message) - : base(message) + /// + public SftpPathNotFoundException(string? message) + : this(message, path: null, innerException: null) { } /// /// Initializes a new instance of the class. /// - /// The message. - /// The inner exception. - public SftpPathNotFoundException(string message, Exception innerException) - : base(message, innerException) + /// + public SftpPathNotFoundException(string? message, string? path) + : this(message, path, innerException: null) { } -#if NETFRAMEWORK /// /// Initializes a new instance of the class. /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// The parameter is . - /// The class name is or is zero (0). - protected SftpPathNotFoundException(SerializationInfo info, StreamingContext context) + /// + public SftpPathNotFoundException(string? message, Exception? innerException) + : this(message, path: null, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The path that cannot be found. + /// The exception that is the cause of the current exception. + public SftpPathNotFoundException(string? message, string? path, Exception? innerException) + : base(Code, string.IsNullOrEmpty(message) ? GetDefaultMessage(path) : message, innerException) + { + Path = path; + } + + private static string GetDefaultMessage(string? path) + { + var message = GetDefaultMessage(Code); + + return path is not null + ? $"{message} Path: '{path}'." + : message; + } + +#if NETFRAMEWORK + /// + protected SftpPathNotFoundException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } -#endif // NETFRAMEWORK +#endif } } diff --git a/src/Renci.SshNet/Common/SftpPermissionDeniedException.cs b/src/Renci.SshNet/Common/SftpPermissionDeniedException.cs index 04301cf32..1f8d9fe36 100644 --- a/src/Renci.SshNet/Common/SftpPermissionDeniedException.cs +++ b/src/Renci.SshNet/Common/SftpPermissionDeniedException.cs @@ -1,7 +1,7 @@ -using System; -#if NETFRAMEWORK -using System.Runtime.Serialization; -#endif // NETFRAMEWORK +#nullable enable +using System; + +using Renci.SshNet.Sftp; namespace Renci.SshNet.Common { @@ -10,47 +10,43 @@ namespace Renci.SshNet.Common /// #if NETFRAMEWORK [Serializable] -#endif // NETFRAMEWORK - public class SftpPermissionDeniedException : SshException +#endif + public class SftpPermissionDeniedException : SftpException { + private const StatusCode Code = StatusCode.PermissionDenied; + /// /// Initializes a new instance of the class. /// public SftpPermissionDeniedException() + : base(Code) { } /// /// Initializes a new instance of the class. /// - /// The message. - public SftpPermissionDeniedException(string message) - : base(message) + /// + public SftpPermissionDeniedException(string? message) + : base(Code, message) { } /// /// Initializes a new instance of the class. /// - /// The message. - /// The inner exception. - public SftpPermissionDeniedException(string message, Exception innerException) - : base(message, innerException) + /// + public SftpPermissionDeniedException(string? message, Exception? innerException) + : base(Code, message, innerException) { } #if NETFRAMEWORK - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// The parameter is . - /// The class name is or is zero (0). - protected SftpPermissionDeniedException(SerializationInfo info, StreamingContext context) + /// + protected SftpPermissionDeniedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } -#endif // NETFRAMEWORK +#endif } } diff --git a/src/Renci.SshNet/Common/SshException.cs b/src/Renci.SshNet/Common/SshException.cs index 440f44885..937eb541c 100644 --- a/src/Renci.SshNet/Common/SshException.cs +++ b/src/Renci.SshNet/Common/SshException.cs @@ -1,16 +1,14 @@ -using System; -#if NETFRAMEWORK -using System.Runtime.Serialization; -#endif // NETFRAMEWORK +#nullable enable +using System; namespace Renci.SshNet.Common { /// - /// The exception that is thrown when SSH exception occurs. + /// The exception that is thrown when an SSH exception occurs. /// #if NETFRAMEWORK [Serializable] -#endif // NETFRAMEWORK +#endif public class SshException : Exception { /// @@ -23,8 +21,8 @@ public SshException() /// /// Initializes a new instance of the class. /// - /// The message. - public SshException(string message) + /// + public SshException(string? message) : base(message) { } @@ -32,25 +30,18 @@ public SshException(string message) /// /// Initializes a new instance of the class. /// - /// The message. - /// The inner. - public SshException(string message, Exception inner) - : base(message, inner) + /// + public SshException(string? message, Exception? innerException) + : base(message, innerException) { } #if NETFRAMEWORK - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// The parameter is . - /// The class name is or is zero (0). - protected SshException(SerializationInfo info, StreamingContext context) + /// + protected SshException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } -#endif // NETFRAMEWORK +#endif } } diff --git a/src/Renci.SshNet/Sftp/Responses/SftpStatusResponse.cs b/src/Renci.SshNet/Sftp/Responses/SftpStatusResponse.cs index 07aaa3f64..305f4cb48 100644 --- a/src/Renci.SshNet/Sftp/Responses/SftpStatusResponse.cs +++ b/src/Renci.SshNet/Sftp/Responses/SftpStatusResponse.cs @@ -1,4 +1,5 @@ -namespace Renci.SshNet.Sftp.Responses +#nullable enable +namespace Renci.SshNet.Sftp.Responses { internal sealed class SftpStatusResponse : SftpResponse { @@ -12,17 +13,17 @@ public SftpStatusResponse(uint protocolVersion) { } - public StatusCodes StatusCode { get; set; } + public StatusCode StatusCode { get; set; } - public string ErrorMessage { get; private set; } + public string? ErrorMessage { get; private set; } - public string Language { get; private set; } + public string? Language { get; private set; } protected override void LoadData() { base.LoadData(); - StatusCode = (StatusCodes)ReadUInt32(); + StatusCode = (StatusCode)ReadUInt32(); if (ProtocolVersion < 3) { diff --git a/src/Renci.SshNet/Sftp/SftpFileStream.cs b/src/Renci.SshNet/Sftp/SftpFileStream.cs index 4eb0a2627..099dd811b 100644 --- a/src/Renci.SshNet/Sftp/SftpFileStream.cs +++ b/src/Renci.SshNet/Sftp/SftpFileStream.cs @@ -282,7 +282,7 @@ private static async Task Open( attributes = session.RequestFStat(handle); } } - catch (SshException ex) + catch (SftpException ex) { session.SessionLoggerFactory.CreateLogger().LogInformation( ex, "fstat failed after opening {Path}. Will set CanSeek=false.", path); diff --git a/src/Renci.SshNet/Sftp/SftpSession.cs b/src/Renci.SshNet/Sftp/SftpSession.cs index 0f04e85f2..8dd7296e0 100644 --- a/src/Renci.SshNet/Sftp/SftpSession.cs +++ b/src/Renci.SshNet/Sftp/SftpSession.cs @@ -415,7 +415,7 @@ private void SendRequest(SftpRequest request) public byte[] RequestOpen(string path, Flags flags, bool nullOnError = false) { byte[] handle = null; - SshException exception = null; + SftpException exception = null; using (var wait = new AutoResetEvent(initialState: false)) { @@ -431,7 +431,7 @@ public byte[] RequestOpen(string path, Flags flags, bool nullOnError = false) }, response => { - exception = GetSftpException(response); + exception = GetSftpException(response, path); wait.SetIgnoringObjectDisposed(); }); @@ -473,7 +473,7 @@ public Task RequestOpenAsync(string path, Flags flags, CancellationToken _encoding, flags, response => tcs.TrySetResult(response.Handle), - response => tcs.TrySetException(GetSftpException(response)))); + response => tcs.TrySetException(GetSftpException(response, path)))); return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken); } @@ -503,7 +503,7 @@ public SftpOpenAsyncResult BeginOpen(string path, Flags flags, AsyncCallback cal }, response => { - asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false); + asyncResult.SetAsCompleted(GetSftpException(response, path), completedSynchronously: false); }); SendRequest(request); @@ -550,7 +550,7 @@ public byte[] EndOpen(SftpOpenAsyncResult asyncResult) /// The handle. public void RequestClose(byte[] handle) { - SshException exception = null; + SftpException exception = null; using (var wait = new AutoResetEvent(initialState: false)) { @@ -596,7 +596,7 @@ public Task RequestCloseAsync(byte[] handle, CancellationToken cancellationToken handle, response => { - if (response.StatusCode == StatusCodes.Ok) + if (response.StatusCode == StatusCode.Ok) { _ = tcs.TrySetResult(true); } @@ -688,7 +688,7 @@ public SftpReadAsyncResult BeginRead(byte[] handle, ulong offset, uint length, A }, response => { - if (response.StatusCode != StatusCodes.Eof) + if (response.StatusCode != StatusCode.Eof) { asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false); } @@ -746,7 +746,7 @@ public byte[] EndRead(SftpReadAsyncResult asyncResult) /// public byte[] RequestRead(byte[] handle, ulong offset, uint length) { - SshException exception = null; + SftpException exception = null; byte[] data = null; @@ -764,7 +764,7 @@ public byte[] RequestRead(byte[] handle, ulong offset, uint length) }, response => { - if (response.StatusCode != StatusCodes.Eof) + if (response.StatusCode != StatusCode.Eof) { exception = GetSftpException(response); } @@ -820,7 +820,7 @@ public Task RequestReadAsync(byte[] handle, ulong offset, uint length, C response => tcs.TrySetResult(response.Data), response => { - if (response.StatusCode == StatusCodes.Eof) + if (response.StatusCode == StatusCode.Eof) { _ = tcs.TrySetResult(Array.Empty()); } @@ -853,7 +853,7 @@ public void RequestWrite(byte[] handle, { Debug.Assert((wait is null) != (writeCompleted is null), "Should have one parameter or the other."); - SshException exception = null; + SftpException exception = null; var request = new SftpWriteRequest(ProtocolVersion, NextRequestId, @@ -918,7 +918,7 @@ public Task RequestWriteAsync(byte[] handle, ulong serverOffset, byte[] data, in length, response => { - if (response.StatusCode == StatusCodes.Ok) + if (response.StatusCode == StatusCode.Ok) { _ = tcs.TrySetResult(true); } @@ -940,7 +940,7 @@ public Task RequestWriteAsync(byte[] handle, ulong serverOffset, byte[] data, in /// public SftpFileAttributes RequestLStat(string path) { - SshException exception = null; + SftpException exception = null; SftpFileAttributes attributes = null; using (var wait = new AutoResetEvent(initialState: false)) @@ -956,7 +956,7 @@ public SftpFileAttributes RequestLStat(string path) }, response => { - exception = GetSftpException(response); + exception = GetSftpException(response, path); wait.SetIgnoringObjectDisposed(); }); @@ -996,7 +996,7 @@ public Task RequestLStatAsync(string path, CancellationToken path, _encoding, response => tcs.TrySetResult(response.Attributes), - response => tcs.TrySetException(GetSftpException(response)))); + response => tcs.TrySetException(GetSftpException(response, path)))); return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken); } @@ -1024,7 +1024,7 @@ public SFtpStatAsyncResult BeginLStat(string path, AsyncCallback callback, objec }, response => { - asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false); + asyncResult.SetAsCompleted(GetSftpException(response, path), completedSynchronously: false); }); SendRequest(request); @@ -1063,7 +1063,7 @@ public SftpFileAttributes EndLStat(SFtpStatAsyncResult asyncResult) /// public SftpFileAttributes RequestFStat(byte[] handle) { - SshException exception = null; + SftpException exception = null; SftpFileAttributes attributes = null; using (var wait = new AutoResetEvent(initialState: false)) @@ -1129,7 +1129,7 @@ public Task RequestFStatAsync(byte[] handle, CancellationTok /// The attributes. public void RequestSetStat(string path, SftpFileAttributes attributes) { - SshException exception = null; + SftpException exception = null; using (var wait = new AutoResetEvent(initialState: false)) { @@ -1140,7 +1140,7 @@ public void RequestSetStat(string path, SftpFileAttributes attributes) attributes, response => { - exception = GetSftpException(response); + exception = GetSftpException(response, path); wait.SetIgnoringObjectDisposed(); }); @@ -1162,7 +1162,7 @@ public void RequestSetStat(string path, SftpFileAttributes attributes) /// The attributes. public void RequestFSetStat(byte[] handle, SftpFileAttributes attributes) { - SshException exception = null; + SftpException exception = null; using (var wait = new AutoResetEvent(initialState: false)) { @@ -1195,7 +1195,7 @@ public void RequestFSetStat(byte[] handle, SftpFileAttributes attributes) /// File handle. public byte[] RequestOpenDir(string path, bool nullOnError = false) { - SshException exception = null; + SftpException exception = null; byte[] handle = null; @@ -1212,7 +1212,7 @@ public byte[] RequestOpenDir(string path, bool nullOnError = false) }, response => { - exception = GetSftpException(response); + exception = GetSftpException(response, path); wait.SetIgnoringObjectDisposed(); }); @@ -1252,7 +1252,7 @@ public Task RequestOpenDirAsync(string path, CancellationToken cancellat path, _encoding, response => tcs.TrySetResult(response.Handle), - response => tcs.TrySetException(GetSftpException(response)))); + response => tcs.TrySetException(GetSftpException(response, path)))); return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken); } @@ -1267,7 +1267,7 @@ public Task RequestOpenDirAsync(string path, CancellationToken cancellat /// public KeyValuePair[] RequestReadDir(byte[] handle) { - SshException exception = null; + SftpException exception = null; KeyValuePair[] result = null; @@ -1283,7 +1283,7 @@ public KeyValuePair[] RequestReadDir(byte[] handle) }, response => { - if (response.StatusCode != StatusCodes.Eof) + if (response.StatusCode != StatusCode.Eof) { exception = GetSftpException(response); } @@ -1330,7 +1330,7 @@ public Task[]> RequestReadDirAsync(byte response => tcs.TrySetResult(response.Files), response => { - if (response.StatusCode == StatusCodes.Eof) + if (response.StatusCode == StatusCode.Eof) { _ = tcs.TrySetResult(null); } @@ -1349,7 +1349,7 @@ public Task[]> RequestReadDirAsync(byte /// The path. public void RequestRemove(string path) { - SshException exception = null; + SftpException exception = null; using (var wait = new AutoResetEvent(initialState: false)) { @@ -1359,7 +1359,7 @@ public void RequestRemove(string path) _encoding, response => { - exception = GetSftpException(response); + exception = GetSftpException(response, path); wait.SetIgnoringObjectDisposed(); }); @@ -1397,13 +1397,13 @@ public Task RequestRemoveAsync(string path, CancellationToken cancellationToken) _encoding, response => { - if (response.StatusCode == StatusCodes.Ok) + if (response.StatusCode == StatusCode.Ok) { _ = tcs.TrySetResult(true); } else { - _ = tcs.TrySetException(GetSftpException(response)); + _ = tcs.TrySetException(GetSftpException(response, path)); } })); @@ -1416,7 +1416,7 @@ public Task RequestRemoveAsync(string path, CancellationToken cancellationToken) /// The path. public void RequestMkDir(string path) { - SshException exception = null; + SftpException exception = null; using (var wait = new AutoResetEvent(initialState: false)) { @@ -1462,7 +1462,7 @@ public Task RequestMkDirAsync(string path, CancellationToken cancellationToken) _encoding, response => { - if (response.StatusCode == StatusCodes.Ok) + if (response.StatusCode == StatusCode.Ok) { _ = tcs.TrySetResult(true); } @@ -1481,7 +1481,7 @@ public Task RequestMkDirAsync(string path, CancellationToken cancellationToken) /// The path. public void RequestRmDir(string path) { - SshException exception = null; + SftpException exception = null; using (var wait = new AutoResetEvent(initialState: false)) { @@ -1491,7 +1491,7 @@ public void RequestRmDir(string path) _encoding, response => { - exception = GetSftpException(response); + exception = GetSftpException(response, path); wait.SetIgnoringObjectDisposed(); }); @@ -1522,7 +1522,7 @@ public Task RequestRmDirAsync(string path, CancellationToken cancellationToken) _encoding, response => { - var exception = GetSftpException(response); + var exception = GetSftpException(response, path); if (exception is not null) { _ = tcs.TrySetException(exception); @@ -1546,7 +1546,7 @@ public Task RequestRmDirAsync(string path, CancellationToken cancellationToken) /// internal KeyValuePair[] RequestRealPath(string path, bool nullOnError = false) { - SshException exception = null; + SftpException exception = null; KeyValuePair[] result = null; @@ -1563,7 +1563,7 @@ internal KeyValuePair[] RequestRealPath(string path, }, response => { - exception = GetSftpException(response); + exception = GetSftpException(response, path); wait.SetIgnoringObjectDisposed(); }); @@ -1602,7 +1602,7 @@ internal Task[]> RequestRealPathAsync(s } else { - _ = tcs.TrySetException(GetSftpException(response)); + _ = tcs.TrySetException(GetSftpException(response, path)); } })); @@ -1627,7 +1627,7 @@ public SftpRealPathAsyncResult BeginRealPath(string path, AsyncCallback callback path, _encoding, response => asyncResult.SetAsCompleted(response.Files[0].Key, completedSynchronously: false), - response => asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false)); + response => asyncResult.SetAsCompleted(GetSftpException(response, path), completedSynchronously: false)); SendRequest(request); return asyncResult; @@ -1672,7 +1672,7 @@ public string EndRealPath(SftpRealPathAsyncResult asyncResult) /// public SftpFileAttributes RequestStat(string path, bool nullOnError = false) { - SshException exception = null; + SftpException exception = null; SftpFileAttributes attributes = null; @@ -1689,7 +1689,7 @@ public SftpFileAttributes RequestStat(string path, bool nullOnError = false) }, response => { - exception = GetSftpException(response); + exception = GetSftpException(response, path); wait.SetIgnoringObjectDisposed(); }); @@ -1724,7 +1724,7 @@ public SFtpStatAsyncResult BeginStat(string path, AsyncCallback callback, object path, _encoding, response => asyncResult.SetAsCompleted(response.Attributes, completedSynchronously: false), - response => asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false)); + response => asyncResult.SetAsCompleted(GetSftpException(response, path), completedSynchronously: false)); SendRequest(request); return asyncResult; @@ -1771,7 +1771,7 @@ public void RequestRename(string oldPath, string newPath) throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_RENAME operation is not supported in {0} version that server operates in.", ProtocolVersion)); } - SshException exception = null; + SftpException exception = null; using (var wait = new AutoResetEvent(initialState: false)) { @@ -1822,7 +1822,7 @@ public Task RequestRenameAsync(string oldPath, string newPath, CancellationToken _encoding, response => { - if (response.StatusCode == StatusCodes.Ok) + if (response.StatusCode == StatusCode.Ok) { _ = tcs.TrySetResult(true); } @@ -1851,7 +1851,7 @@ internal KeyValuePair[] RequestReadLink(string path, throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_READLINK operation is not supported in {0} version that server operates in.", ProtocolVersion)); } - SshException exception = null; + SftpException exception = null; KeyValuePair[] result = null; @@ -1868,7 +1868,7 @@ internal KeyValuePair[] RequestReadLink(string path, }, response => { - exception = GetSftpException(response); + exception = GetSftpException(response, path); wait.SetIgnoringObjectDisposed(); }); @@ -1897,7 +1897,7 @@ public void RequestSymLink(string linkpath, string targetpath) throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_SYMLINK operation is not supported in {0} version that server operates in.", ProtocolVersion)); } - SshException exception = null; + SftpException exception = null; using (var wait = new AutoResetEvent(initialState: false)) { @@ -1935,7 +1935,7 @@ public void RequestPosixRename(string oldPath, string newPath) throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion)); } - SshException exception = null; + SftpException exception = null; using (var wait = new AutoResetEvent(initialState: false)) { @@ -1981,7 +1981,7 @@ public SftpFileSystemInformation RequestStatVfs(string path, bool nullOnError = throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion)); } - SshException exception = null; + SftpException exception = null; SftpFileSystemInformation information = null; @@ -1998,7 +1998,7 @@ public SftpFileSystemInformation RequestStatVfs(string path, bool nullOnError = }, response => { - exception = GetSftpException(response); + exception = GetSftpException(response, path); wait.SetIgnoringObjectDisposed(); }); @@ -2049,7 +2049,7 @@ public Task RequestStatVfsAsync(string path, Cancella path, _encoding, response => tcs.TrySetResult(response.GetReply().Information), - response => tcs.TrySetException(GetSftpException(response)))); + response => tcs.TrySetException(GetSftpException(response, path)))); return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken); } @@ -2070,7 +2070,7 @@ internal SftpFileSystemInformation RequestFStatVfs(byte[] handle, bool nullOnErr throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion)); } - SshException exception = null; + SftpException exception = null; SftpFileSystemInformation information = null; @@ -2120,7 +2120,7 @@ internal void HardLink(string oldPath, string newPath) throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion)); } - SshException exception = null; + SftpException exception = null; using (var wait = new AutoResetEvent(initialState: false)) { @@ -2209,19 +2209,28 @@ public uint CalculateOptimalWriteLength(uint bufferSize, byte[] handle) return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields; } - internal static SshException GetSftpException(SftpStatusResponse response) + internal static SftpException GetSftpException(SftpStatusResponse response, string path = null) { #pragma warning disable IDE0010 // Add missing cases switch (response.StatusCode) { - case StatusCodes.Ok: + case StatusCode.Ok: return null; - case StatusCodes.PermissionDenied: + case StatusCode.PermissionDenied: return new SftpPermissionDeniedException(response.ErrorMessage); - case StatusCodes.NoSuchFile: - return new SftpPathNotFoundException(response.ErrorMessage); + case StatusCode.NoSuchFile: + + var message = response.ErrorMessage; + + if (!string.IsNullOrEmpty(message) && path is not null) + { + message = $"{message}{(message[^1] == '.' ? " " : ". ")}Path: '{path}'."; + } + + return new SftpPathNotFoundException(message, path); + default: - return new SshException(response.ErrorMessage); + return new SftpException(response.StatusCode, response.ErrorMessage); } #pragma warning restore IDE0010 // Add missing cases } diff --git a/src/Renci.SshNet/Sftp/StatusCode.cs b/src/Renci.SshNet/Sftp/StatusCode.cs new file mode 100644 index 000000000..8ec6dfbb3 --- /dev/null +++ b/src/Renci.SshNet/Sftp/StatusCode.cs @@ -0,0 +1,86 @@ +namespace Renci.SshNet.Sftp +{ + /// + /// Specifies status codes returned by the server in response to SFTP requests. + /// + public enum StatusCode + { + /// + /// SSH_FX_OK. + /// + /// + /// The operation completed successfully. + /// + Ok = 0, + + /// + /// SSH_FX_EOF. + /// + /// + /// An attempt was made to read past the end of the file, + /// or no more directory entries were available. + /// + Eof = 1, + + /// + /// SSH_FX_NO_SUCH_FILE. + /// + /// + /// A reference was made to a file that does not exist. + /// + NoSuchFile = 2, + + /// + /// SSH_FX_PERMISSION_DENIED. + /// + /// + /// The user does not have sufficient permissions to perform the operation. + /// + PermissionDenied = 3, + + /// + /// SSH_FX_FAILURE. + /// + /// + /// An error occurred, but no specific error code exists to describe + /// the failure. + /// + Failure = 4, + + /// + /// SSH_FX_BAD_MESSAGE. + /// + /// + /// A badly formatted packet or SFTP protocol incompatibility was detected. + /// + BadMessage = 5, + + /// + /// SSH_FX_NO_CONNECTION. + /// + /// + /// A pseudo-error which indicates that the client has no + /// connection to the server (it can only be generated locally + /// by the client, and MUST NOT be returned by servers). + /// + NoConnection = 6, + + /// + /// SSH_FX_CONNECTION_LOST. + /// + /// + /// A pseudo-error which indicates that the connection to the + /// server has been lost (it can only be generated locally by + /// the client, and MUST NOT be returned by servers). + /// + ConnectionLost = 7, + + /// + /// SSH_FX_OP_UNSUPPORTED. + /// + /// + /// The operation could not be completed because the server did not support it. + /// + OperationUnsupported = 8, + } +} diff --git a/src/Renci.SshNet/Sftp/StatusCodes.cs b/src/Renci.SshNet/Sftp/StatusCodes.cs deleted file mode 100644 index 12997a47e..000000000 --- a/src/Renci.SshNet/Sftp/StatusCodes.cs +++ /dev/null @@ -1,165 +0,0 @@ -namespace Renci.SshNet.Sftp -{ - internal enum StatusCodes : uint - { - /// - /// SSH_FX_OK. - /// - Ok = 0, - - /// - /// SSH_FX_EOF. - /// - Eof = 1, - - /// - /// SSH_FX_NO_SUCH_FILE. - /// - NoSuchFile = 2, - - /// - /// SSH_FX_PERMISSION_DENIED. - /// - PermissionDenied = 3, - - /// - /// SSH_FX_FAILURE. - /// - Failure = 4, - - /// - /// SSH_FX_BAD_MESSAGE. - /// - BadMessage = 5, - - /// - /// SSH_FX_NO_CONNECTION. - /// - NoConnection = 6, - - /// - /// SSH_FX_CONNECTION_LOST. - /// - ConnectionLost = 7, - - /// - /// SSH_FX_OP_UNSUPPORTED. - /// - OperationUnsupported = 8, - - /// - /// SSH_FX_INVALID_HANDLE. - /// - InvalidHandle = 9, - - /// - /// SSH_FX_NO_SUCH_PATH. - /// - NoSuchPath = 10, - - /// - /// SSH_FX_FILE_ALREADY_EXISTS. - /// - FileAlreadyExists = 11, - - /// - /// SSH_FX_WRITE_PROTECT. - /// - WriteProtect = 12, - - /// - /// SSH_FX_NO_MEDIA. - /// - NoMedia = 13, - - /// - /// SSH_FX_NO_SPACE_ON_FILESYSTEM. - /// - NoSpaceOnFilesystem = 14, - - /// - /// SSH_FX_QUOTA_EXCEEDED. - /// - QuotaExceeded = 15, - - /// - /// SSH_FX_UNKNOWN_PRINCIPAL. - /// - UnknownPrincipal = 16, - - /// - /// SSH_FX_LOCK_CONFLICT. - /// - LockConflict = 17, - - /// - /// SSH_FX_DIR_NOT_EMPTY. - /// - DirNotEmpty = 18, - - /// - /// SSH_FX_NOT_A_DIRECTORY. - /// - NotDirectory = 19, - - /// - /// SSH_FX_INVALID_FILENAME. - /// - InvalidFilename = 20, - - /// - /// SSH_FX_LINK_LOOP. - /// - LinkLoop = 21, - - /// - /// SSH_FX_CANNOT_DELETE. - /// - CannotDelete = 22, - - /// - /// SSH_FX_INVALID_PARAMETER. - /// - InvalidParameter = 23, - - /// - /// SSH_FX_FILE_IS_A_DIRECTORY. - /// - FileIsADirectory = 24, - - /// - /// SSH_FX_BYTE_RANGE_LOCK_CONFLICT. - /// - ByteRangeLockConflict = 25, - - /// - /// SSH_FX_BYTE_RANGE_LOCK_REFUSED. - /// - ByteRangeLockRefused = 26, - - /// - /// SSH_FX_DELETE_PENDING. - /// - DeletePending = 27, - - /// - /// SSH_FX_FILE_CORRUPT. - /// - FileCorrupt = 28, - - /// - /// SSH_FX_OWNER_INVALID. - /// - OwnerInvalid = 29, - - /// - /// SSH_FX_GROUP_INVALID. - /// - GroupInvalid = 30, - - /// - /// SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK. - /// - NoMatchingByteRangeLock = 31 - } -} diff --git a/src/Renci.SshNet/SftpClient.cs b/src/Renci.SshNet/SftpClient.cs index 2139ff76a..77014e9a3 100644 --- a/src/Renci.SshNet/SftpClient.cs +++ b/src/Renci.SshNet/SftpClient.cs @@ -2476,7 +2476,7 @@ private async Task InternalUploadFile( return; } - Debug.Assert(s.StatusCode == StatusCodes.Ok); + Debug.Assert(s.StatusCode == StatusCode.Ok); asyncResult?.Update(writtenBytes); diff --git a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.CreateDirectory.cs b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.CreateDirectory.cs index d78abbcc0..098ee824e 100644 --- a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.CreateDirectory.cs +++ b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.CreateDirectory.cs @@ -1,5 +1,6 @@  using Renci.SshNet.Common; +using Renci.SshNet.Sftp; namespace Renci.SshNet.IntegrationTests.OldIntegrationTests { @@ -56,7 +57,8 @@ public void Test_Sftp_CreateDirectory_Already_Exists() sftp.CreateDirectory("test"); - Assert.ThrowsExactly(() => sftp.CreateDirectory("test")); + var ex = Assert.ThrowsExactly(() => sftp.CreateDirectory("test")); + Assert.AreEqual(StatusCode.Failure, ex.StatusCode); } } } diff --git a/test/Renci.SshNet.IntegrationTests/SftpTests.cs b/test/Renci.SshNet.IntegrationTests/SftpTests.cs index ad7a19319..bc404d439 100644 --- a/test/Renci.SshNet.IntegrationTests/SftpTests.cs +++ b/test/Renci.SshNet.IntegrationTests/SftpTests.cs @@ -266,7 +266,8 @@ public void Sftp_Create_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -393,7 +394,8 @@ public void Sftp_AppendAllLines_NoEncoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -513,7 +515,8 @@ public void Sftp_AppendAllText_NoEncoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -637,7 +640,8 @@ public void Sftp_AppendText_NoEncoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -759,7 +763,8 @@ public void Sftp_AppendAllLines_Encoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally @@ -877,7 +882,8 @@ public void Sftp_AppendAllText_Encoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -999,7 +1005,8 @@ public void Sftp_AppendText_Encoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -1125,7 +1132,8 @@ public void Sftp_CreateText_NoEncoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -1269,7 +1277,8 @@ public void Sftp_CreateText_Encoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -1361,7 +1370,8 @@ public void Sftp_DownloadFile_FileDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); // ensure file was not created by us Assert.IsFalse(client.Exists(remoteFile)); @@ -1399,7 +1409,8 @@ public void Sftp_ReadAllBytes_FileDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); // ensure file was not created by us Assert.IsFalse(client.Exists(remoteFile)); @@ -1482,7 +1493,8 @@ public void Sftp_ReadAllLines_NoEncoding_FileDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); // ensure file was not created by us Assert.IsFalse(client.Exists(remoteFile)); @@ -1567,7 +1579,8 @@ public void Sftp_ReadAllLines_Encoding_FileDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); // ensure file was not created by us Assert.IsFalse(client.Exists(remoteFile)); @@ -1649,7 +1662,8 @@ public void Sftp_ReadAllText_NoEncoding_FileDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); // ensure file was not created by us Assert.IsFalse(client.Exists(remoteFile)); @@ -1733,7 +1747,8 @@ public void Sftp_ReadAllText_Encoding_FileDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); // ensure file was not created by us Assert.IsFalse(client.Exists(remoteFile)); @@ -1830,7 +1845,8 @@ public void Sftp_ReadLines_FileDoesNotExist(bool encoding) }); Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); // ensure file was not created by us Assert.IsFalse(client.Exists(remoteFile)); @@ -1913,7 +1929,8 @@ public void Sftp_WriteAllBytes_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -2036,7 +2053,8 @@ public void Sftp_WriteAllLines_IEnumerable_NoEncoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -2164,7 +2182,8 @@ public void Sftp_WriteAllLines_IEnumerable_Encoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -2290,7 +2309,8 @@ public void Sftp_WriteAllLines_Array_NoEncoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -2416,7 +2436,8 @@ public void Sftp_WriteAllLines_Array_Encoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -2543,7 +2564,8 @@ public void Sftp_WriteAllText_NoEncoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -2670,7 +2692,8 @@ public void Sftp_WriteAllText_Encoding_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); } finally { @@ -2799,7 +2822,8 @@ public void Sftp_BeginDownloadFile_FileDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteFile}'.", ex.Message); + Assert.AreEqual(remoteFile, ex.Path); // ensure file was not created by us Assert.IsFalse(client.Exists(remoteFile)); @@ -2844,7 +2868,8 @@ public void Sftp_BeginListDirectory_DirectoryDoesNotExist() catch (SftpPathNotFoundException ex) { Assert.IsNull(ex.InnerException); - Assert.AreEqual("No such file", ex.Message); + Assert.AreEqual($"No such file. Path: '{remoteDirectory}'.", ex.Message); + Assert.AreEqual(remoteDirectory, ex.Path); // ensure directory was not created by us Assert.IsFalse(client.Exists(remoteDirectory)); @@ -3098,17 +3123,8 @@ public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsFalse_E var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile, false, null, null); - try - { - client.EndUploadFile(asyncResult); - Assert.Fail(); - } - catch (SshException ex) - { - Assert.AreEqual(typeof(SshException), ex.GetType()); - Assert.IsNull(ex.InnerException); - Assert.AreEqual("Failure", ex.Message); - } + var ex = Assert.Throws(() => client.EndUploadFile(asyncResult)); + Assert.AreEqual(StatusCode.Failure, ex.StatusCode); } finally { diff --git a/test/Renci.SshNet.Tests/Classes/Common/SftpExceptionTest.cs b/test/Renci.SshNet.Tests/Classes/Common/SftpExceptionTest.cs new file mode 100644 index 000000000..5ce69fbab --- /dev/null +++ b/test/Renci.SshNet.Tests/Classes/Common/SftpExceptionTest.cs @@ -0,0 +1,66 @@ +using System; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Renci.SshNet.Common; +using Renci.SshNet.Sftp; + +namespace Renci.SshNet.Tests.Classes.Common +{ + [TestClass] + public class SftpExceptionTest + { + [TestMethod] + public void StatusCodes() + { + Assert.AreEqual(StatusCode.BadMessage, new SftpException(StatusCode.BadMessage).StatusCode); + Assert.AreEqual(StatusCode.OperationUnsupported, new SftpException(StatusCode.OperationUnsupported, null).StatusCode); + Assert.AreEqual(StatusCode.Failure, new SftpException(StatusCode.Failure, null, null).StatusCode); + + Assert.AreEqual(StatusCode.PermissionDenied, new SftpPermissionDeniedException().StatusCode); + Assert.AreEqual(StatusCode.PermissionDenied, new SftpPermissionDeniedException(null).StatusCode); + Assert.AreEqual(StatusCode.PermissionDenied, new SftpPermissionDeniedException(null, null).StatusCode); + + Assert.AreEqual(StatusCode.NoSuchFile, new SftpPathNotFoundException().StatusCode); + Assert.AreEqual(StatusCode.NoSuchFile, new SftpPathNotFoundException(null).StatusCode); + Assert.AreEqual(StatusCode.NoSuchFile, new SftpPathNotFoundException(null, path: null).StatusCode); + Assert.AreEqual(StatusCode.NoSuchFile, new SftpPathNotFoundException(null, innerException: null).StatusCode); + Assert.AreEqual(StatusCode.NoSuchFile, new SftpPathNotFoundException(null, null, null).StatusCode); + } + + [TestMethod] + public void Message() + { + Assert.IsFalse(string.IsNullOrWhiteSpace(new SftpException(StatusCode.Failure).Message)); + Assert.IsFalse(string.IsNullOrWhiteSpace(new SftpException(StatusCode.Failure, "").Message)); + Assert.AreEqual("Custom message", new SftpException(StatusCode.Failure, "Custom message").Message); + + Assert.IsFalse(string.IsNullOrWhiteSpace(new SftpPermissionDeniedException().Message)); + Assert.IsFalse(string.IsNullOrWhiteSpace(new SftpPermissionDeniedException("").Message)); + Assert.IsFalse(string.IsNullOrWhiteSpace(new SftpPermissionDeniedException("", null).Message)); + Assert.AreEqual("Custom message1", new SftpPermissionDeniedException("Custom message1").Message); + Assert.AreEqual("Custom message2", new SftpPermissionDeniedException("Custom message2", null).Message); + + Assert.IsFalse(string.IsNullOrWhiteSpace(new SftpPathNotFoundException().Message)); + Assert.IsFalse(string.IsNullOrWhiteSpace(new SftpPathNotFoundException("").Message)); + Assert.IsFalse(string.IsNullOrWhiteSpace(new SftpPathNotFoundException("", path: null).Message)); + Assert.AreEqual("Custom message1", new SftpPathNotFoundException("Custom message1").Message); + Assert.AreEqual("Custom message2", new SftpPathNotFoundException("Custom message2", path: null).Message); + Assert.AreEqual("Custom message2", new SftpPathNotFoundException("Custom message2", "path1").Message); + Assert.AreEqual("Custom message3", new SftpPathNotFoundException("Custom message3", innerException: null).Message); + Assert.AreEqual("Custom message4", new SftpPathNotFoundException("Custom message4", null, null).Message); + } + + [TestMethod] + public void PathNotFoundException_Path() + { + Assert.IsNull(new SftpPathNotFoundException().Path); + Assert.IsNull(new SftpPathNotFoundException("message").Path); + Assert.AreEqual("path1", new SftpPathNotFoundException("message", "path1").Path); + Assert.AreEqual("path2", new SftpPathNotFoundException(null, "path2", null).Path); + + Assert.Contains("Path: 'path3'.", new SftpPathNotFoundException(message: null, path: "path3").Message, StringComparison.Ordinal); + Assert.Contains("Path: 'path4'.", new SftpPathNotFoundException(message: "", path: "path4").Message, StringComparison.Ordinal); + } + } +} diff --git a/test/Renci.SshNet.Tests/Classes/SftpClientTest.UploadFile.cs b/test/Renci.SshNet.Tests/Classes/SftpClientTest.UploadFile.cs index 38a4b85e0..63c0a241b 100644 --- a/test/Renci.SshNet.Tests/Classes/SftpClientTest.UploadFile.cs +++ b/test/Renci.SshNet.Tests/Classes/SftpClientTest.UploadFile.cs @@ -145,7 +145,7 @@ public void SendMessage(Message message) else if (dataMsg.Data[sizeof(uint)] == (byte)SftpMessageTypes.Write) { // Fail the 5th write request - var statusCode = ++_numWriteRequests == 5 ? StatusCodes.PermissionDenied : StatusCodes.Ok; + var statusCode = ++_numWriteRequests == 5 ? StatusCode.PermissionDenied : StatusCode.Ok; var responseId = ++_numRequests; // Dispatch the responses on a different thread to simulate reality. diff --git a/version.json b/version.json index a020f8478..15ff28b39 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "2025.0.1-prerelease.{height}", + "version": "2025.1.0-prerelease.{height}", "assemblyVersion": { "precision": "revision" },