Add SftpException and SftpPathNotFoundException.Path (#1716)

This adds an SftpException which sits between the existing SftpPathNotFoundException/
SftpPermissionDeniedException and SshException, and which contains the response code
from the SSH_FXP_STATUS packet, along with a default message if one was not provided.

SftpPathNotFoundException also gains a Path property which is populated in cases where
it makes sense.
This commit is contained in:
Rob Hague
2025-10-23 07:34:26 +02:00
committed by GitHub
parent dccdedc36e
commit cd9ec8f395
18 changed files with 479 additions and 393 deletions
@@ -1,5 +1,6 @@

using Renci.SshNet.Common;
using Renci.SshNet.Sftp;
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
@@ -56,7 +57,8 @@ namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
sftp.CreateDirectory("test");
Assert.ThrowsExactly<SshException>(() => sftp.CreateDirectory("test"));
var ex = Assert.ThrowsExactly<SftpException>(() => sftp.CreateDirectory("test"));
Assert.AreEqual(StatusCode.Failure, ex.StatusCode);
}
}
}
+52 -36
View File
@@ -266,7 +266,8 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
});
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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 @@ namespace Renci.SshNet.IntegrationTests
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<SftpException>(() => client.EndUploadFile(asyncResult));
Assert.AreEqual(StatusCode.Failure, ex.StatusCode);
}
finally
{
@@ -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);
}
}
}
@@ -145,7 +145,7 @@ namespace Renci.SshNet.Tests.Classes
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.