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"
},