Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions src/Renci.SshNet/Common/SftpException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ public class SftpException : SshException
/// </summary>
public StatusCode StatusCode { get; }

/// <summary>
/// Gets the path that is associated with this exception.
/// </summary>
/// <value>
/// The path that the failed operation was performed on, or <see langword="null"/>
/// if no path was passed to the constructor for this instance.
/// </value>
/// <remarks>
/// This is populated only for operations which act on a single path, such as
/// opening or deleting a file. It is <see langword="null"/> for operations acting
/// on an open handle or on more than one path, such as renaming a file.
/// </remarks>
public string? Path { get; }

/// <summary>
/// Initializes a new instance of the <see cref="SftpException"/> class.
/// </summary>
Expand All @@ -46,15 +60,28 @@ public SftpException(StatusCode statusCode, string? message)
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public SftpException(StatusCode statusCode, string? message, Exception? innerException)
: base(string.IsNullOrEmpty(message) ? GetDefaultMessage(statusCode) : message, innerException)
: this(statusCode, message, path: null, innerException)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SftpException"/> class.
/// </summary>
/// <param name="statusCode">The status code that indicates the error that occurred.</param>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="path">The path that the failed operation was performed on.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public SftpException(StatusCode statusCode, string? message, string? path, Exception? innerException)
: base(string.IsNullOrEmpty(message) ? GetDefaultMessage(statusCode, path) : message, innerException)
{
StatusCode = statusCode;
Path = path;
}

private protected static string GetDefaultMessage(StatusCode statusCode)
private static string GetDefaultMessage(StatusCode statusCode, string? path)
{
#pragma warning disable IDE0072 // Add missing cases
return statusCode switch
var message = statusCode switch
{
StatusCode.Ok => "The operation completed successfully.",
StatusCode.NoSuchFile => "A reference was made to a file that does not exist.",
Expand All @@ -65,6 +92,10 @@ private protected static string GetDefaultMessage(StatusCode statusCode)
_ => statusCode.ToString()
};
#pragma warning restore IDE0072 // Add missing cases

return path is not null
? $"{message} Path: '{path}'."
: message;
}

#if NETFRAMEWORK
Expand Down
21 changes: 1 addition & 20 deletions src/Renci.SshNet/Common/SftpPathNotFoundException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,6 @@ public class SftpPathNotFoundException : SftpException
{
private const StatusCode Code = StatusCode.NoSuchFile;

/// <summary>
/// Gets the path that cannot be found.
/// </summary>
/// <value>
/// The path that cannot be found, or <see langword="null"/> if no path was
/// passed to the constructor for this instance.
/// </value>
public string? Path { get; }

/// <summary>
/// Initializes a new instance of the <see cref="SftpPathNotFoundException"/> class.
/// </summary>
Expand Down Expand Up @@ -66,18 +57,8 @@ public SftpPathNotFoundException(string? message, Exception? innerException)
/// <param name="path">The path that cannot be found.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public SftpPathNotFoundException(string? message, string? path, Exception? innerException)
: base(Code, string.IsNullOrEmpty(message) ? GetDefaultMessage(path) : message, innerException)
: base(Code, message, path, innerException)
{
Path = path;
}

private static string GetDefaultMessage(string? path)
{
var message = GetDefaultMessage(Code);

return path is not null
? $"{message} Path: '{path}'."
: message;
}

#if NETFRAMEWORK
Expand Down
13 changes: 12 additions & 1 deletion src/Renci.SshNet/Common/SftpPermissionDeniedException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,18 @@ public SftpPermissionDeniedException(string? message)
/// </summary>
/// <inheritdoc cref="Exception(string, Exception)" path="/param"/>
public SftpPermissionDeniedException(string? message, Exception? innerException)
: base(Code, message, innerException)
: this(message, path: null, innerException)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SftpPermissionDeniedException"/> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="path">The path that the failed operation was performed on.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public SftpPermissionDeniedException(string? message, string? path, Exception? innerException)
: base(Code, message, path, innerException)
{
}

Expand Down
24 changes: 11 additions & 13 deletions src/Renci.SshNet/Sftp/SftpSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1034,7 +1034,7 @@ public void RequestMkDir(string path)
_encoding,
response =>
{
exception = GetSftpException(response);
exception = GetSftpException(response, path);
wait.SetIgnoringObjectDisposed();
});

Expand Down Expand Up @@ -1071,7 +1071,7 @@ public Task RequestMkDirAsync(string path, CancellationToken cancellationToken)
}
else
{
_ = tcs.TrySetException(GetSftpException(response));
_ = tcs.TrySetException(GetSftpException(response, path));
}
}));

Expand Down Expand Up @@ -1519,25 +1519,23 @@ public uint CalculateOptimalWriteLength(uint bufferSize, byte[] handle)
internal static SftpException GetSftpException(SftpStatusResponse response, string path = null)
{
#pragma warning disable IDE0010 // Add missing cases
var message = response.ErrorMessage;

if (!string.IsNullOrEmpty(message) && path is not null)
{
message = $"{message}{(message[^1] == '.' ? " " : ". ")}Path: '{path}'.";
}

switch (response.StatusCode)
{
case StatusCode.Ok:
return null;
case StatusCode.PermissionDenied:
return new SftpPermissionDeniedException(response.ErrorMessage);
return new SftpPermissionDeniedException(message, path, innerException: null);
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 SftpException(response.StatusCode, response.ErrorMessage);
return new SftpException(response.StatusCode, message, path, innerException: null);
}
#pragma warning restore IDE0010 // Add missing cases
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public void Test_Sftp_CreateDirectory_In_Forbidden_Directory()
{
sftp.Connect();

Assert.ThrowsExactly<SftpPermissionDeniedException>(() => sftp.CreateDirectory("/sbin/test"));
var ex = Assert.ThrowsExactly<SftpPermissionDeniedException>(() => sftp.CreateDirectory("/sbin/test"));
Assert.AreEqual("/sbin/test", ex.Path);
}
}

Expand All @@ -43,7 +44,8 @@ public void Test_Sftp_CreateDirectory_Invalid_Path()
{
sftp.Connect();

Assert.ThrowsExactly<SftpPathNotFoundException>(() => sftp.CreateDirectory("/abcdefg/abcefg"));
var ex = Assert.ThrowsExactly<SftpPathNotFoundException>(() => sftp.CreateDirectory("/abcdefg/abcefg"));
Assert.AreEqual("/abcdefg/abcefg", ex.Path);
}
}

Expand All @@ -59,6 +61,7 @@ public void Test_Sftp_CreateDirectory_Already_Exists()

var ex = Assert.ThrowsExactly<SftpException>(() => sftp.CreateDirectory("test"));
Assert.AreEqual(StatusCode.Failure, ex.StatusCode);
Assert.AreEqual($"{sftp.WorkingDirectory}/test", ex.Path);
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions test/Renci.SshNet.IntegrationTests/SftpClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Renci.SshNet.Common;
using Renci.SshNet.Common;

namespace Renci.SshNet.IntegrationTests
{
Expand Down Expand Up @@ -104,7 +104,8 @@ public async Task Create_directory_with_contents_and_list_it_async()
[TestMethod]
public void Test_Sftp_ListDirectory_Permission_Denied()
{
Assert.ThrowsExactly<SftpPermissionDeniedException>(() => _sftpClient.ListDirectory("/root"));
var ex = Assert.ThrowsExactly<SftpPermissionDeniedException>(() => _sftpClient.ListDirectory("/root"));
Assert.AreEqual("/root", ex.Path);
}

[TestMethod]
Expand Down
16 changes: 15 additions & 1 deletion test/Renci.SshNet.Tests/Classes/Common/SftpExceptionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ 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.Failure, new SftpException(StatusCode.Failure, null, 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.PermissionDenied, new SftpPermissionDeniedException(null, null, null).StatusCode);

Assert.AreEqual(StatusCode.NoSuchFile, new SftpPathNotFoundException().StatusCode);
Assert.AreEqual(StatusCode.NoSuchFile, new SftpPathNotFoundException(null).StatusCode);
Expand All @@ -34,12 +36,14 @@ 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.AreEqual("Custom message", new SftpException(StatusCode.Failure, "Custom message", "path1", null).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.AreEqual("Custom message3", new SftpPermissionDeniedException("Custom message3", "path1", null).Message);

Assert.IsFalse(string.IsNullOrWhiteSpace(new SftpPathNotFoundException().Message));
Assert.IsFalse(string.IsNullOrWhiteSpace(new SftpPathNotFoundException("").Message));
Expand All @@ -52,8 +56,18 @@ public void Message()
}

[TestMethod]
public void PathNotFoundException_Path()
public void Path()
{
Assert.IsNull(new SftpException(StatusCode.Failure).Path);
Assert.IsNull(new SftpException(StatusCode.Failure, "message", innerException: null).Path);
Assert.AreEqual("path1", new SftpException(StatusCode.Failure, null, "path1", null).Path);
Assert.Contains("Path: 'path2'.", new SftpException(StatusCode.Failure, null, "path2", null).Message, StringComparison.Ordinal);

Assert.IsNull(new SftpPermissionDeniedException().Path);
Assert.IsNull(new SftpPermissionDeniedException("message").Path);
Assert.AreEqual("path1", new SftpPermissionDeniedException("message", "path1", null).Path);
Assert.Contains("Path: 'path2'.", new SftpPermissionDeniedException("", "path2", null).Message, StringComparison.Ordinal);

Assert.IsNull(new SftpPathNotFoundException().Path);
Assert.IsNull(new SftpPathNotFoundException("message").Path);
Assert.AreEqual("path1", new SftpPathNotFoundException("message", "path1").Path);
Expand Down