diff --git a/Directory.Build.props b/Directory.Build.props index 284b26d09..b263d4fa9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -23,6 +23,13 @@ true + + + $(NoWarn);CS8602 + + diff --git a/src/Renci.SshNet/NetConfClient.cs b/src/Renci.SshNet/NetConfClient.cs index 49be11766..e9ebace45 100644 --- a/src/Renci.SshNet/NetConfClient.cs +++ b/src/Renci.SshNet/NetConfClient.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Diagnostics.CodeAnalysis; using System.Net; using System.Threading; @@ -19,7 +20,7 @@ public class NetConfClient : BaseClient /// /// Holds instance that used to communicate to the server. /// - private INetConfSession _netConfSession; + private INetConfSession? _netConfSession; /// /// Gets or sets the operation timeout. @@ -47,7 +48,7 @@ public TimeSpan OperationTimeout /// /// The current NetConf session. /// - internal INetConfSession NetConfSession + internal INetConfSession? NetConfSession { get { return _netConfSession; } } @@ -160,9 +161,18 @@ internal NetConfClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, I /// /// The NetConf server capabilities. /// + /// Client is not connected. public XmlDocument ServerCapabilities { - get { return _netConfSession.ServerCapabilities; } + get + { + if (_netConfSession is null) + { + throw new SshConnectionException("Client not connected."); + } + + return _netConfSession.ServerCapabilities; + } } /// @@ -171,9 +181,18 @@ public XmlDocument ServerCapabilities /// /// The NetConf client capabilities. /// + /// Client is not connected. public XmlDocument ClientCapabilities { - get { return _netConfSession.ClientCapabilities; } + get + { + if (_netConfSession is null) + { + throw new SshConnectionException("Client not connected."); + } + + return _netConfSession.ClientCapabilities; + } } /// @@ -196,6 +215,11 @@ public XmlDocument ClientCapabilities /// Client is not connected. public XmlDocument SendReceiveRpc(XmlDocument rpc) { + if (_netConfSession is null) + { + throw new SshConnectionException("Client not connected."); + } + return _netConfSession.SendReceiveRpc(rpc, AutomaticMessageIdHandling); } @@ -222,6 +246,11 @@ public XmlDocument SendReceiveRpc(string xml) /// Client is not connected. public XmlDocument SendCloseRpc() { + if (_netConfSession is null) + { + throw new SshConnectionException("Client not connected."); + } + var rpc = new XmlDocument(); rpc.LoadXml(""); return _netConfSession.SendReceiveRpc(rpc, AutomaticMessageIdHandling); @@ -244,7 +273,7 @@ protected override void OnDisconnecting() { base.OnDisconnecting(); - _netConfSession.Disconnect(); + _netConfSession?.Disconnect(); } /// diff --git a/src/Renci.SshNet/ScpClient.cs b/src/Renci.SshNet/ScpClient.cs index 8dd42a215..0355a0a55 100644 --- a/src/Renci.SshNet/ScpClient.cs +++ b/src/Renci.SshNet/ScpClient.cs @@ -1,5 +1,7 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; @@ -127,12 +129,12 @@ public IRemotePathTransformation RemotePathTransformation /// /// Occurs when downloading file. /// - public event EventHandler Downloading; + public event EventHandler? Downloading; /// /// Occurs when uploading file. /// - public event EventHandler Uploading; + public event EventHandler? Uploading; /// /// Initializes a new instance of the class. @@ -246,8 +248,14 @@ internal ScpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServ /// is a zero-length . /// A directory with the specified path exists on the remote host. /// The secure copy execution request was rejected by the server. + /// Client is not connected. public void Upload(Stream source, string path) { + if (Session is null) + { + throw new SshConnectionException("Client not connected."); + } + var posixPath = PosixPath.CreateAbsoluteOrRelativeFilePath(path); using (var input = ServiceFactory.CreatePipeStream()) @@ -280,6 +288,7 @@ public void Upload(Stream source, string path) /// is a zero-length . /// A directory with the specified path exists on the remote host. /// The secure copy execution request was rejected by the server. + /// Client is not connected. public void Upload(FileInfo fileInfo, string path) { if (fileInfo is null) @@ -287,6 +296,11 @@ public void Upload(FileInfo fileInfo, string path) throw new ArgumentNullException(nameof(fileInfo)); } + if (Session is null) + { + throw new SshConnectionException("Client not connected."); + } + var posixPath = PosixPath.CreateAbsoluteOrRelativeFilePath(path); using (var input = ServiceFactory.CreatePipeStream()) @@ -323,6 +337,7 @@ public void Upload(FileInfo fileInfo, string path) /// is a zero-length string. /// does not exist on the remote host, is not a directory or the user does not have the required permission. /// The secure copy execution request was rejected by the server. + /// Client is not connected. public void Upload(DirectoryInfo directoryInfo, string path) { if (directoryInfo is null) @@ -340,6 +355,11 @@ public void Upload(DirectoryInfo directoryInfo, string path) throw new ArgumentException("The path cannot be a zero-length string.", nameof(path)); } + if (Session is null) + { + throw new SshConnectionException("Client not connected."); + } + using (var input = ServiceFactory.CreatePipeStream()) using (var channel = Session.CreateChannelSession()) { @@ -371,6 +391,7 @@ public void Upload(DirectoryInfo directoryInfo, string path) /// is or empty. /// exists on the remote host, and is not a regular file. /// The secure copy execution request was rejected by the server. + /// Client is not connected. public void Download(string filename, FileInfo fileInfo) { if (string.IsNullOrEmpty(filename)) @@ -383,6 +404,11 @@ public void Download(string filename, FileInfo fileInfo) throw new ArgumentNullException(nameof(fileInfo)); } + if (Session is null) + { + throw new SshConnectionException("Client not connected."); + } + using (var input = ServiceFactory.CreatePipeStream()) using (var channel = Session.CreateChannelSession()) { @@ -411,6 +437,7 @@ public void Download(string filename, FileInfo fileInfo) /// is . /// File or directory with the specified path does not exist on the remote host. /// The secure copy execution request was rejected by the server. + /// Client is not connected. public void Download(string directoryName, DirectoryInfo directoryInfo) { if (string.IsNullOrEmpty(directoryName)) @@ -423,6 +450,11 @@ public void Download(string directoryName, DirectoryInfo directoryInfo) throw new ArgumentNullException(nameof(directoryInfo)); } + if (Session is null) + { + throw new SshConnectionException("Client not connected."); + } + using (var input = ServiceFactory.CreatePipeStream()) using (var channel = Session.CreateChannelSession()) { @@ -451,6 +483,7 @@ public void Download(string directoryName, DirectoryInfo directoryInfo) /// is . /// exists on the remote host, and is not a regular file. /// The secure copy execution request was rejected by the server. + /// Client is not connected. public void Download(string filename, Stream destination) { if (string.IsNullOrWhiteSpace(filename)) @@ -463,6 +496,11 @@ public void Download(string filename, Stream destination) throw new ArgumentNullException(nameof(destination)); } + if (Session is null) + { + throw new SshConnectionException("Client not connected."); + } + using (var input = ServiceFactory.CreatePipeStream()) using (var channel = Session.CreateChannelSession()) { @@ -767,13 +805,17 @@ private void InternalDownload(IChannelSession channel, Stream input, FileSystemI directoryCounter--; - currentDirectoryFullName = new DirectoryInfo(currentDirectoryFullName).Parent.FullName; - if (directoryCounter == 0) { break; } + var currentDirectoryParent = new DirectoryInfo(currentDirectoryFullName).Parent; + + Debug.Assert(currentDirectoryParent is not null, $"Should be {directoryCounter.ToString(CultureInfo.InvariantCulture)} levels deeper than {startDirectoryFullName}."); + + currentDirectoryFullName = currentDirectoryParent.FullName; + continue; } @@ -795,7 +837,7 @@ private void InternalDownload(IChannelSession channel, Stream input, FileSystemI else { // Don't create directory for first level - newDirectoryInfo = fileSystemInfo as DirectoryInfo; + newDirectoryInfo = (DirectoryInfo)fileSystemInfo; } directoryCounter++; diff --git a/src/Renci.SshNet/SshClient.cs b/src/Renci.SshNet/SshClient.cs index e702e99f8..6517a4268 100644 --- a/src/Renci.SshNet/SshClient.cs +++ b/src/Renci.SshNet/SshClient.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -27,7 +28,7 @@ public class SshClient : BaseClient /// private bool _isDisposed; - private MemoryStream _inputStream; + private MemoryStream? _inputStream; /// /// Gets the list of forwarded ports. @@ -272,7 +273,7 @@ public SshCommand RunCommand(string commandText) /// Returns a representation of a object. /// /// Client is not connected. - public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes, int bufferSize) + public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary? terminalModes, int bufferSize) { EnsureSessionIsOpen(); @@ -333,7 +334,7 @@ public Shell CreateShell(Stream input, Stream output, Stream extendedOutput) /// Returns a representation of a object. /// /// Client is not connected. - public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes, int bufferSize) + public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary? terminalModes, int bufferSize) { /* * TODO Issue #1224: let shell dispose of input stream when we own the stream! @@ -442,7 +443,7 @@ public ShellStream CreateShellStream(string terminalName, uint columns, uint row /// to the drawable area of the window. /// /// - public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, IDictionary terminalModeValues) + public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, IDictionary? terminalModeValues) { EnsureSessionIsOpen();