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,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
/// </summary>
#if NETFRAMEWORK
[Serializable]
#endif // NETFRAMEWORK
#endif
public class NetConfServerException : SshException
{
/// <summary>
@@ -23,8 +21,8 @@ namespace Renci.SshNet.Common
/// <summary>
/// Initializes a new instance of the <see cref="NetConfServerException"/> class.
/// </summary>
/// <param name="message">The message.</param>
public NetConfServerException(string message)
/// <inheritdoc cref="Exception(string)" path="/param"/>
public NetConfServerException(string? message)
: base(message)
{
}
@@ -32,25 +30,18 @@ namespace Renci.SshNet.Common
/// <summary>
/// Initializes a new instance of the <see cref="NetConfServerException"/> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="innerException">The inner exception.</param>
public NetConfServerException(string message, Exception innerException)
/// <inheritdoc cref="Exception(string, Exception)" path="/param"/>
public NetConfServerException(string? message, Exception? innerException)
: base(message, innerException)
{
}
#if NETFRAMEWORK
/// <summary>
/// Initializes a new instance of the <see cref="NetConfServerException"/> class.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param>
/// <exception cref="ArgumentNullException">The <paramref name="info"/> parameter is <see langword="null"/>.</exception>
/// <exception cref="SerializationException">The class name is <see langword="null"/> or <see cref="Exception.HResult"/> is zero (0). </exception>
protected NetConfServerException(SerializationInfo info, StreamingContext context)
/// <inheritdoc/>
protected NetConfServerException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
}
#endif // NETFRAMEWORK
#endif
}
}
+10 -19
View File
@@ -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
/// </summary>
#if NETFRAMEWORK
[Serializable]
#endif // NETFRAMEWORK
#endif
public class ProxyException : SshException
{
/// <summary>
@@ -23,8 +21,8 @@ namespace Renci.SshNet.Common
/// <summary>
/// Initializes a new instance of the <see cref="ProxyException"/> class.
/// </summary>
/// <param name="message">The message.</param>
public ProxyException(string message)
/// <inheritdoc cref="Exception(string)" path="/param"/>
public ProxyException(string? message)
: base(message)
{
}
@@ -32,25 +30,18 @@ namespace Renci.SshNet.Common
/// <summary>
/// Initializes a new instance of the <see cref="ProxyException"/> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="innerException">The inner exception.</param>
public ProxyException(string message, Exception innerException)
/// <inheritdoc cref="Exception(string, Exception)" path="/param"/>
public ProxyException(string? message, Exception? innerException)
: base(message, innerException)
{
}
#if NETFRAMEWORK
/// <summary>
/// Initializes a new instance of the <see cref="ProxyException"/> class.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param>
/// <exception cref="ArgumentNullException">The <paramref name="info"/> parameter is <see langword="null"/>.</exception>
/// <exception cref="SerializationException">The class name is <see langword="null"/> or <see cref="Exception.HResult"/> is zero (0).</exception>
protected ProxyException(SerializationInfo info, StreamingContext context)
/// <inheritdoc/>
protected ProxyException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
}
#endif // NETFRAMEWORK
#endif
}
}
+11 -20
View File
@@ -1,16 +1,14 @@
using System;
#if NETFRAMEWORK
using System.Runtime.Serialization;
#endif // NETFRAMEWORK
#nullable enable
using System;
namespace Renci.SshNet.Common
{
/// <summary>
/// The exception that is thrown when SCP error occurred.
/// The exception that is thrown when an SCP error occurs.
/// </summary>
#if NETFRAMEWORK
[Serializable]
#endif // NETFRAMEWORK
#endif
public class ScpException : SshException
{
/// <summary>
@@ -23,8 +21,8 @@ namespace Renci.SshNet.Common
/// <summary>
/// Initializes a new instance of the <see cref="ScpException"/> class.
/// </summary>
/// <param name="message">The message.</param>
public ScpException(string message)
/// <inheritdoc cref="Exception(string)" path="/param"/>
public ScpException(string? message)
: base(message)
{
}
@@ -32,25 +30,18 @@ namespace Renci.SshNet.Common
/// <summary>
/// Initializes a new instance of the <see cref="ScpException"/> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="innerException">The inner exception.</param>
public ScpException(string message, Exception innerException)
/// <inheritdoc cref="Exception(string, Exception)" path="/param"/>
public ScpException(string? message, Exception? innerException)
: base(message, innerException)
{
}
#if NETFRAMEWORK
/// <summary>
/// Initializes a new instance of the <see cref="ScpException"/> class.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param>
/// <exception cref="ArgumentNullException">The <paramref name="info"/> parameter is <see langword="null"/>.</exception>
/// <exception cref="SerializationException">The class name is <see langword="null"/> or <see cref="Exception.HResult"/> is zero (0). </exception>
protected ScpException(SerializationInfo info, StreamingContext context)
/// <inheritdoc/>
protected ScpException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
}
#endif // NETFRAMEWORK
#endif
}
}
+76
View File
@@ -0,0 +1,76 @@
#nullable enable
using System;
using Renci.SshNet.Sftp;
namespace Renci.SshNet.Common
{
/// <summary>
/// The exception that is thrown when an error occurs in the SFTP layer.
/// </summary>
#if NETFRAMEWORK
[Serializable]
#endif
public class SftpException : SshException
{
/// <summary>
/// Gets the status code that is associated with this exception.
/// </summary>
public StatusCode StatusCode { get; }
/// <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>
public SftpException(StatusCode statusCode)
: this(statusCode, message: null, innerException: null)
{
}
/// <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>
public SftpException(StatusCode statusCode, string? message)
: this(statusCode, message, innerException: null)
{
}
/// <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="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)
{
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
/// <inheritdoc/>
protected SftpException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
}
#endif
}
}
@@ -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
/// </summary>
#if NETFRAMEWORK
[Serializable]
#endif // NETFRAMEWORK
public class SftpPathNotFoundException : SshException
#endif
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>
public SftpPathNotFoundException()
: this(message: null, path: null, innerException: null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SftpPathNotFoundException"/> class.
/// </summary>
/// <param name="message">The message.</param>
public SftpPathNotFoundException(string message)
: base(message)
/// <inheritdoc cref="Exception(string)" path="/param"/>
public SftpPathNotFoundException(string? message)
: this(message, path: null, innerException: null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SftpPathNotFoundException"/> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="innerException">The inner exception.</param>
public SftpPathNotFoundException(string message, Exception innerException)
: base(message, innerException)
/// <inheritdoc cref="Exception(string)" path="/param"/>
public SftpPathNotFoundException(string? message, string? path)
: this(message, path, innerException: null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SftpPathNotFoundException"/> class.
/// </summary>
/// <inheritdoc cref="Exception(string, Exception)" path="/param"/>
public SftpPathNotFoundException(string? message, Exception? innerException)
: this(message, path: null, innerException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SftpPathNotFoundException"/> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <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)
{
Path = path;
}
private static string GetDefaultMessage(string? path)
{
var message = GetDefaultMessage(Code);
return path is not null
? $"{message} Path: '{path}'."
: message;
}
#if NETFRAMEWORK
/// <summary>
/// Initializes a new instance of the <see cref="SftpPathNotFoundException"/> class.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param>
/// <exception cref="ArgumentNullException">The <paramref name="info"/> parameter is <see langword="null"/>.</exception>
/// <exception cref="SerializationException">The class name is <see langword="null"/> or <see cref="Exception.HResult"/> is zero (0). </exception>
protected SftpPathNotFoundException(SerializationInfo info, StreamingContext context)
/// <inheritdoc/>
protected SftpPathNotFoundException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
}
#endif // NETFRAMEWORK
#endif
}
}
@@ -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
/// </summary>
#if NETFRAMEWORK
[Serializable]
#endif // NETFRAMEWORK
public class SftpPermissionDeniedException : SshException
#endif
public class SftpPermissionDeniedException : SftpException
{
private const StatusCode Code = StatusCode.PermissionDenied;
/// <summary>
/// Initializes a new instance of the <see cref="SftpPermissionDeniedException"/> class.
/// </summary>
public SftpPermissionDeniedException()
: base(Code)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SftpPermissionDeniedException"/> class.
/// </summary>
/// <param name="message">The message.</param>
public SftpPermissionDeniedException(string message)
: base(message)
/// <inheritdoc cref="Exception(string)" path="/param"/>
public SftpPermissionDeniedException(string? message)
: base(Code, message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SftpPermissionDeniedException"/> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="innerException">The inner exception.</param>
public SftpPermissionDeniedException(string message, Exception innerException)
: base(message, innerException)
/// <inheritdoc cref="Exception(string, Exception)" path="/param"/>
public SftpPermissionDeniedException(string? message, Exception? innerException)
: base(Code, message, innerException)
{
}
#if NETFRAMEWORK
/// <summary>
/// Initializes a new instance of the <see cref="SftpPermissionDeniedException"/> class.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param>
/// <exception cref="ArgumentNullException">The <paramref name="info"/> parameter is <see langword="null"/>.</exception>
/// <exception cref="SerializationException">The class name is <see langword="null"/> or <see cref="Exception.HResult"/> is zero (0). </exception>
protected SftpPermissionDeniedException(SerializationInfo info, StreamingContext context)
/// <inheritdoc/>
protected SftpPermissionDeniedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
}
#endif // NETFRAMEWORK
#endif
}
}
+12 -21
View File
@@ -1,16 +1,14 @@
using System;
#if NETFRAMEWORK
using System.Runtime.Serialization;
#endif // NETFRAMEWORK
#nullable enable
using System;
namespace Renci.SshNet.Common
{
/// <summary>
/// The exception that is thrown when SSH exception occurs.
/// The exception that is thrown when an SSH exception occurs.
/// </summary>
#if NETFRAMEWORK
[Serializable]
#endif // NETFRAMEWORK
#endif
public class SshException : Exception
{
/// <summary>
@@ -23,8 +21,8 @@ namespace Renci.SshNet.Common
/// <summary>
/// Initializes a new instance of the <see cref="SshException"/> class.
/// </summary>
/// <param name="message">The message.</param>
public SshException(string message)
/// <inheritdoc cref="Exception(string, Exception)" path="/param"/>
public SshException(string? message)
: base(message)
{
}
@@ -32,25 +30,18 @@ namespace Renci.SshNet.Common
/// <summary>
/// Initializes a new instance of the <see cref="SshException"/> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="inner">The inner.</param>
public SshException(string message, Exception inner)
: base(message, inner)
/// <inheritdoc cref="Exception(string, Exception)" path="/param"/>
public SshException(string? message, Exception? innerException)
: base(message, innerException)
{
}
#if NETFRAMEWORK
/// <summary>
/// Initializes a new instance of the <see cref="SshException"/> class.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param>
/// <exception cref="ArgumentNullException">The <paramref name="info"/> parameter is <see langword="null"/>.</exception>
/// <exception cref="SerializationException">The class name is <see langword="null"/> or <see cref="Exception.HResult"/> is zero (0). </exception>
protected SshException(SerializationInfo info, StreamingContext context)
/// <inheritdoc/>
protected SshException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
}
#endif // NETFRAMEWORK
#endif
}
}
@@ -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 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)
{
+1 -1
View File
@@ -282,7 +282,7 @@ namespace Renci.SshNet.Sftp
attributes = session.RequestFStat(handle);
}
}
catch (SshException ex)
catch (SftpException ex)
{
session.SessionLoggerFactory.CreateLogger<SftpFileStream>().LogInformation(
ex, "fstat failed after opening {Path}. Will set CanSeek=false.", path);
+68 -59
View File
@@ -415,7 +415,7 @@ namespace Renci.SshNet.Sftp
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 @@ namespace Renci.SshNet.Sftp
},
response =>
{
exception = GetSftpException(response);
exception = GetSftpException(response, path);
wait.SetIgnoringObjectDisposed();
});
@@ -473,7 +473,7 @@ namespace Renci.SshNet.Sftp
_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 @@ namespace Renci.SshNet.Sftp
},
response =>
{
asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false);
asyncResult.SetAsCompleted(GetSftpException(response, path), completedSynchronously: false);
});
SendRequest(request);
@@ -550,7 +550,7 @@ namespace Renci.SshNet.Sftp
/// <param name="handle">The handle.</param>
public void RequestClose(byte[] handle)
{
SshException exception = null;
SftpException exception = null;
using (var wait = new AutoResetEvent(initialState: false))
{
@@ -596,7 +596,7 @@ namespace Renci.SshNet.Sftp
handle,
response =>
{
if (response.StatusCode == StatusCodes.Ok)
if (response.StatusCode == StatusCode.Ok)
{
_ = tcs.TrySetResult(true);
}
@@ -688,7 +688,7 @@ namespace Renci.SshNet.Sftp
},
response =>
{
if (response.StatusCode != StatusCodes.Eof)
if (response.StatusCode != StatusCode.Eof)
{
asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false);
}
@@ -746,7 +746,7 @@ namespace Renci.SshNet.Sftp
/// </returns>
public byte[] RequestRead(byte[] handle, ulong offset, uint length)
{
SshException exception = null;
SftpException exception = null;
byte[] data = null;
@@ -764,7 +764,7 @@ namespace Renci.SshNet.Sftp
},
response =>
{
if (response.StatusCode != StatusCodes.Eof)
if (response.StatusCode != StatusCode.Eof)
{
exception = GetSftpException(response);
}
@@ -820,7 +820,7 @@ namespace Renci.SshNet.Sftp
response => tcs.TrySetResult(response.Data),
response =>
{
if (response.StatusCode == StatusCodes.Eof)
if (response.StatusCode == StatusCode.Eof)
{
_ = tcs.TrySetResult(Array.Empty<byte>());
}
@@ -853,7 +853,7 @@ namespace Renci.SshNet.Sftp
{
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 @@ namespace Renci.SshNet.Sftp
length,
response =>
{
if (response.StatusCode == StatusCodes.Ok)
if (response.StatusCode == StatusCode.Ok)
{
_ = tcs.TrySetResult(true);
}
@@ -940,7 +940,7 @@ namespace Renci.SshNet.Sftp
/// </returns>
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 @@ namespace Renci.SshNet.Sftp
},
response =>
{
exception = GetSftpException(response);
exception = GetSftpException(response, path);
wait.SetIgnoringObjectDisposed();
});
@@ -996,7 +996,7 @@ namespace Renci.SshNet.Sftp
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 @@ namespace Renci.SshNet.Sftp
},
response =>
{
asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false);
asyncResult.SetAsCompleted(GetSftpException(response, path), completedSynchronously: false);
});
SendRequest(request);
@@ -1063,7 +1063,7 @@ namespace Renci.SshNet.Sftp
/// <inheritdoc/>
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 @@ namespace Renci.SshNet.Sftp
/// <param name="attributes">The attributes.</param>
public void RequestSetStat(string path, SftpFileAttributes attributes)
{
SshException exception = null;
SftpException exception = null;
using (var wait = new AutoResetEvent(initialState: false))
{
@@ -1140,7 +1140,7 @@ namespace Renci.SshNet.Sftp
attributes,
response =>
{
exception = GetSftpException(response);
exception = GetSftpException(response, path);
wait.SetIgnoringObjectDisposed();
});
@@ -1162,7 +1162,7 @@ namespace Renci.SshNet.Sftp
/// <param name="attributes">The attributes.</param>
public void RequestFSetStat(byte[] handle, SftpFileAttributes attributes)
{
SshException exception = null;
SftpException exception = null;
using (var wait = new AutoResetEvent(initialState: false))
{
@@ -1195,7 +1195,7 @@ namespace Renci.SshNet.Sftp
/// <returns>File handle.</returns>
public byte[] RequestOpenDir(string path, bool nullOnError = false)
{
SshException exception = null;
SftpException exception = null;
byte[] handle = null;
@@ -1212,7 +1212,7 @@ namespace Renci.SshNet.Sftp
},
response =>
{
exception = GetSftpException(response);
exception = GetSftpException(response, path);
wait.SetIgnoringObjectDisposed();
});
@@ -1252,7 +1252,7 @@ namespace Renci.SshNet.Sftp
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 @@ namespace Renci.SshNet.Sftp
/// </returns>
public KeyValuePair<string, SftpFileAttributes>[] RequestReadDir(byte[] handle)
{
SshException exception = null;
SftpException exception = null;
KeyValuePair<string, SftpFileAttributes>[] result = null;
@@ -1283,7 +1283,7 @@ namespace Renci.SshNet.Sftp
},
response =>
{
if (response.StatusCode != StatusCodes.Eof)
if (response.StatusCode != StatusCode.Eof)
{
exception = GetSftpException(response);
}
@@ -1330,7 +1330,7 @@ namespace Renci.SshNet.Sftp
response => tcs.TrySetResult(response.Files),
response =>
{
if (response.StatusCode == StatusCodes.Eof)
if (response.StatusCode == StatusCode.Eof)
{
_ = tcs.TrySetResult(null);
}
@@ -1349,7 +1349,7 @@ namespace Renci.SshNet.Sftp
/// <param name="path">The path.</param>
public void RequestRemove(string path)
{
SshException exception = null;
SftpException exception = null;
using (var wait = new AutoResetEvent(initialState: false))
{
@@ -1359,7 +1359,7 @@ namespace Renci.SshNet.Sftp
_encoding,
response =>
{
exception = GetSftpException(response);
exception = GetSftpException(response, path);
wait.SetIgnoringObjectDisposed();
});
@@ -1397,13 +1397,13 @@ namespace Renci.SshNet.Sftp
_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 @@ namespace Renci.SshNet.Sftp
/// <param name="path">The path.</param>
public void RequestMkDir(string path)
{
SshException exception = null;
SftpException exception = null;
using (var wait = new AutoResetEvent(initialState: false))
{
@@ -1462,7 +1462,7 @@ namespace Renci.SshNet.Sftp
_encoding,
response =>
{
if (response.StatusCode == StatusCodes.Ok)
if (response.StatusCode == StatusCode.Ok)
{
_ = tcs.TrySetResult(true);
}
@@ -1481,7 +1481,7 @@ namespace Renci.SshNet.Sftp
/// <param name="path">The path.</param>
public void RequestRmDir(string path)
{
SshException exception = null;
SftpException exception = null;
using (var wait = new AutoResetEvent(initialState: false))
{
@@ -1491,7 +1491,7 @@ namespace Renci.SshNet.Sftp
_encoding,
response =>
{
exception = GetSftpException(response);
exception = GetSftpException(response, path);
wait.SetIgnoringObjectDisposed();
});
@@ -1522,7 +1522,7 @@ namespace Renci.SshNet.Sftp
_encoding,
response =>
{
var exception = GetSftpException(response);
var exception = GetSftpException(response, path);
if (exception is not null)
{
_ = tcs.TrySetException(exception);
@@ -1546,7 +1546,7 @@ namespace Renci.SshNet.Sftp
/// </returns>
internal KeyValuePair<string, SftpFileAttributes>[] RequestRealPath(string path, bool nullOnError = false)
{
SshException exception = null;
SftpException exception = null;
KeyValuePair<string, SftpFileAttributes>[] result = null;
@@ -1563,7 +1563,7 @@ namespace Renci.SshNet.Sftp
},
response =>
{
exception = GetSftpException(response);
exception = GetSftpException(response, path);
wait.SetIgnoringObjectDisposed();
});
@@ -1602,7 +1602,7 @@ namespace Renci.SshNet.Sftp
}
else
{
_ = tcs.TrySetException(GetSftpException(response));
_ = tcs.TrySetException(GetSftpException(response, path));
}
}));
@@ -1627,7 +1627,7 @@ namespace Renci.SshNet.Sftp
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 @@ namespace Renci.SshNet.Sftp
/// </returns>
public SftpFileAttributes RequestStat(string path, bool nullOnError = false)
{
SshException exception = null;
SftpException exception = null;
SftpFileAttributes attributes = null;
@@ -1689,7 +1689,7 @@ namespace Renci.SshNet.Sftp
},
response =>
{
exception = GetSftpException(response);
exception = GetSftpException(response, path);
wait.SetIgnoringObjectDisposed();
});
@@ -1724,7 +1724,7 @@ namespace Renci.SshNet.Sftp
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 @@ namespace Renci.SshNet.Sftp
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 @@ namespace Renci.SshNet.Sftp
_encoding,
response =>
{
if (response.StatusCode == StatusCodes.Ok)
if (response.StatusCode == StatusCode.Ok)
{
_ = tcs.TrySetResult(true);
}
@@ -1851,7 +1851,7 @@ namespace Renci.SshNet.Sftp
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<string, SftpFileAttributes>[] result = null;
@@ -1868,7 +1868,7 @@ namespace Renci.SshNet.Sftp
},
response =>
{
exception = GetSftpException(response);
exception = GetSftpException(response, path);
wait.SetIgnoringObjectDisposed();
});
@@ -1897,7 +1897,7 @@ namespace Renci.SshNet.Sftp
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 @@ namespace Renci.SshNet.Sftp
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 @@ namespace Renci.SshNet.Sftp
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 @@ namespace Renci.SshNet.Sftp
},
response =>
{
exception = GetSftpException(response);
exception = GetSftpException(response, path);
wait.SetIgnoringObjectDisposed();
});
@@ -2049,7 +2049,7 @@ namespace Renci.SshNet.Sftp
path,
_encoding,
response => tcs.TrySetResult(response.GetReply<StatVfsReplyInfo>().Information),
response => tcs.TrySetException(GetSftpException(response))));
response => tcs.TrySetException(GetSftpException(response, path))));
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
}
@@ -2070,7 +2070,7 @@ namespace Renci.SshNet.Sftp
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 @@ namespace Renci.SshNet.Sftp
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 @@ namespace Renci.SshNet.Sftp
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
}
+86
View File
@@ -0,0 +1,86 @@
namespace Renci.SshNet.Sftp
{
/// <summary>
/// Specifies status codes returned by the server in response to SFTP requests.
/// </summary>
public enum StatusCode
{
/// <summary>
/// SSH_FX_OK.
/// </summary>
/// <remarks>
/// The operation completed successfully.
/// </remarks>
Ok = 0,
/// <summary>
/// SSH_FX_EOF.
/// </summary>
/// <remarks>
/// An attempt was made to read past the end of the file,
/// or no more directory entries were available.
/// </remarks>
Eof = 1,
/// <summary>
/// SSH_FX_NO_SUCH_FILE.
/// </summary>
/// <remarks>
/// A reference was made to a file that does not exist.
/// </remarks>
NoSuchFile = 2,
/// <summary>
/// SSH_FX_PERMISSION_DENIED.
/// </summary>
/// <remarks>
/// The user does not have sufficient permissions to perform the operation.
/// </remarks>
PermissionDenied = 3,
/// <summary>
/// SSH_FX_FAILURE.
/// </summary>
/// <remarks>
/// An error occurred, but no specific error code exists to describe
/// the failure.
/// </remarks>
Failure = 4,
/// <summary>
/// SSH_FX_BAD_MESSAGE.
/// </summary>
/// <remarks>
/// A badly formatted packet or SFTP protocol incompatibility was detected.
/// </remarks>
BadMessage = 5,
/// <summary>
/// SSH_FX_NO_CONNECTION.
/// </summary>
/// <remarks>
/// 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).
/// </remarks>
NoConnection = 6,
/// <summary>
/// SSH_FX_CONNECTION_LOST.
/// </summary>
/// <remarks>
/// 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).
/// </remarks>
ConnectionLost = 7,
/// <summary>
/// SSH_FX_OP_UNSUPPORTED.
/// </summary>
/// <remarks>
/// The operation could not be completed because the server did not support it.
/// </remarks>
OperationUnsupported = 8,
}
}
-165
View File
@@ -1,165 +0,0 @@
namespace Renci.SshNet.Sftp
{
internal enum StatusCodes : uint
{
/// <summary>
/// SSH_FX_OK.
/// </summary>
Ok = 0,
/// <summary>
/// SSH_FX_EOF.
/// </summary>
Eof = 1,
/// <summary>
/// SSH_FX_NO_SUCH_FILE.
/// </summary>
NoSuchFile = 2,
/// <summary>
/// SSH_FX_PERMISSION_DENIED.
/// </summary>
PermissionDenied = 3,
/// <summary>
/// SSH_FX_FAILURE.
/// </summary>
Failure = 4,
/// <summary>
/// SSH_FX_BAD_MESSAGE.
/// </summary>
BadMessage = 5,
/// <summary>
/// SSH_FX_NO_CONNECTION.
/// </summary>
NoConnection = 6,
/// <summary>
/// SSH_FX_CONNECTION_LOST.
/// </summary>
ConnectionLost = 7,
/// <summary>
/// SSH_FX_OP_UNSUPPORTED.
/// </summary>
OperationUnsupported = 8,
/// <summary>
/// SSH_FX_INVALID_HANDLE.
/// </summary>
InvalidHandle = 9,
/// <summary>
/// SSH_FX_NO_SUCH_PATH.
/// </summary>
NoSuchPath = 10,
/// <summary>
/// SSH_FX_FILE_ALREADY_EXISTS.
/// </summary>
FileAlreadyExists = 11,
/// <summary>
/// SSH_FX_WRITE_PROTECT.
/// </summary>
WriteProtect = 12,
/// <summary>
/// SSH_FX_NO_MEDIA.
/// </summary>
NoMedia = 13,
/// <summary>
/// SSH_FX_NO_SPACE_ON_FILESYSTEM.
/// </summary>
NoSpaceOnFilesystem = 14,
/// <summary>
/// SSH_FX_QUOTA_EXCEEDED.
/// </summary>
QuotaExceeded = 15,
/// <summary>
/// SSH_FX_UNKNOWN_PRINCIPAL.
/// </summary>
UnknownPrincipal = 16,
/// <summary>
/// SSH_FX_LOCK_CONFLICT.
/// </summary>
LockConflict = 17,
/// <summary>
/// SSH_FX_DIR_NOT_EMPTY.
/// </summary>
DirNotEmpty = 18,
/// <summary>
/// SSH_FX_NOT_A_DIRECTORY.
/// </summary>
NotDirectory = 19,
/// <summary>
/// SSH_FX_INVALID_FILENAME.
/// </summary>
InvalidFilename = 20,
/// <summary>
/// SSH_FX_LINK_LOOP.
/// </summary>
LinkLoop = 21,
/// <summary>
/// SSH_FX_CANNOT_DELETE.
/// </summary>
CannotDelete = 22,
/// <summary>
/// SSH_FX_INVALID_PARAMETER.
/// </summary>
InvalidParameter = 23,
/// <summary>
/// SSH_FX_FILE_IS_A_DIRECTORY.
/// </summary>
FileIsADirectory = 24,
/// <summary>
/// SSH_FX_BYTE_RANGE_LOCK_CONFLICT.
/// </summary>
ByteRangeLockConflict = 25,
/// <summary>
/// SSH_FX_BYTE_RANGE_LOCK_REFUSED.
/// </summary>
ByteRangeLockRefused = 26,
/// <summary>
/// SSH_FX_DELETE_PENDING.
/// </summary>
DeletePending = 27,
/// <summary>
/// SSH_FX_FILE_CORRUPT.
/// </summary>
FileCorrupt = 28,
/// <summary>
/// SSH_FX_OWNER_INVALID.
/// </summary>
OwnerInvalid = 29,
/// <summary>
/// SSH_FX_GROUP_INVALID.
/// </summary>
GroupInvalid = 30,
/// <summary>
/// SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK.
/// </summary>
NoMatchingByteRangeLock = 31
}
}
+1 -1
View File
@@ -2476,7 +2476,7 @@ namespace Renci.SshNet
return;
}
Debug.Assert(s.StatusCode == StatusCodes.Ok);
Debug.Assert(s.StatusCode == StatusCode.Ok);
asyncResult?.Update(writtenBytes);
@@ -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.
+1 -1
View File
@@ -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"
},