Quellcode durchsuchen

Enable nullable on NetConf/Scp/SshClient (#1392)

* Enable nullable on NetConf/Scp/SshClient

* fix formatting

* improve directoryCounter check

* disable nullable warnings on old frameworks

since the libraries are missing a lot of nullable attributes
in the old frameworks, this causes a lot of false positive
nullable warnings.

Simply disable these warnings for old frameworks.
mus65 vor 1 Jahr
Ursprung
Commit
b4722b86e8

+ 7 - 0
Directory.Build.props

@@ -23,6 +23,13 @@
     <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
   </PropertyGroup>
 
+  <!--
+        Disable nullable warnings on old frameworks because of missing annotations.
+  -->
+  <PropertyGroup Condition=" !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0')) ">
+    <NoWarn>$(NoWarn);CS8602</NoWarn>
+  </PropertyGroup>
+
   <!--
         Add the stylecop config to each project.
     -->

+ 35 - 6
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 @@ namespace Renci.SshNet
         /// <summary>
         /// Holds <see cref="INetConfSession"/> instance that used to communicate to the server.
         /// </summary>
-        private INetConfSession _netConfSession;
+        private INetConfSession? _netConfSession;
 
         /// <summary>
         /// Gets or sets the operation timeout.
@@ -47,7 +48,7 @@ namespace Renci.SshNet
         /// <value>
         /// The current NetConf session.
         /// </value>
-        internal INetConfSession NetConfSession
+        internal INetConfSession? NetConfSession
         {
             get { return _netConfSession; }
         }
@@ -160,9 +161,18 @@ namespace Renci.SshNet
         /// <value>
         /// The NetConf server capabilities.
         /// </value>
+        /// <exception cref="SshConnectionException">Client is not connected.</exception>
         public XmlDocument ServerCapabilities
         {
-            get { return _netConfSession.ServerCapabilities; }
+            get
+            {
+                if (_netConfSession is null)
+                {
+                    throw new SshConnectionException("Client not connected.");
+                }
+
+                return _netConfSession.ServerCapabilities;
+            }
         }
 
         /// <summary>
@@ -171,9 +181,18 @@ namespace Renci.SshNet
         /// <value>
         /// The NetConf client capabilities.
         /// </value>
+        /// <exception cref="SshConnectionException">Client is not connected.</exception>
         public XmlDocument ClientCapabilities
         {
-            get { return _netConfSession.ClientCapabilities; }
+            get
+            {
+                if (_netConfSession is null)
+                {
+                    throw new SshConnectionException("Client not connected.");
+                }
+
+                return _netConfSession.ClientCapabilities;
+            }
         }
 
         /// <summary>
@@ -196,6 +215,11 @@ namespace Renci.SshNet
         /// <exception cref="SshConnectionException">Client is not connected.</exception>
         public XmlDocument SendReceiveRpc(XmlDocument rpc)
         {
+            if (_netConfSession is null)
+            {
+                throw new SshConnectionException("Client not connected.");
+            }
+
             return _netConfSession.SendReceiveRpc(rpc, AutomaticMessageIdHandling);
         }
 
@@ -222,6 +246,11 @@ namespace Renci.SshNet
         /// <exception cref="SshConnectionException">Client is not connected.</exception>
         public XmlDocument SendCloseRpc()
         {
+            if (_netConfSession is null)
+            {
+                throw new SshConnectionException("Client not connected.");
+            }
+
             var rpc = new XmlDocument();
             rpc.LoadXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rpc message-id=\"6666\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><close-session/></rpc>");
             return _netConfSession.SendReceiveRpc(rpc, AutomaticMessageIdHandling);
@@ -244,7 +273,7 @@ namespace Renci.SshNet
         {
             base.OnDisconnecting();
 
-            _netConfSession.Disconnect();
+            _netConfSession?.Disconnect();
         }
 
         /// <summary>

+ 48 - 6
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 @@ namespace Renci.SshNet
         /// <summary>
         /// Occurs when downloading file.
         /// </summary>
-        public event EventHandler<ScpDownloadEventArgs> Downloading;
+        public event EventHandler<ScpDownloadEventArgs>? Downloading;
 
         /// <summary>
         /// Occurs when uploading file.
         /// </summary>
-        public event EventHandler<ScpUploadEventArgs> Uploading;
+        public event EventHandler<ScpUploadEventArgs>? Uploading;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ScpClient"/> class.
@@ -246,8 +248,14 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="path"/> is a zero-length <see cref="string"/>.</exception>
         /// <exception cref="ScpException">A directory with the specified path exists on the remote host.</exception>
         /// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
+        /// <exception cref="SshConnectionException">Client is not connected.</exception>
         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 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="path"/> is a zero-length <see cref="string"/>.</exception>
         /// <exception cref="ScpException">A directory with the specified path exists on the remote host.</exception>
         /// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
+        /// <exception cref="SshConnectionException">Client is not connected.</exception>
         public void Upload(FileInfo fileInfo, string path)
         {
             if (fileInfo is null)
@@ -287,6 +296,11 @@ namespace Renci.SshNet
                 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 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="path"/> is a zero-length string.</exception>
         /// <exception cref="ScpException"><paramref name="path"/> does not exist on the remote host, is not a directory or the user does not have the required permission.</exception>
         /// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
+        /// <exception cref="SshConnectionException">Client is not connected.</exception>
         public void Upload(DirectoryInfo directoryInfo, string path)
         {
             if (directoryInfo is null)
@@ -340,6 +355,11 @@ namespace Renci.SshNet
                 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 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="filename"/> is <see langword="null"/> or empty.</exception>
         /// <exception cref="ScpException"><paramref name="filename"/> exists on the remote host, and is not a regular file.</exception>
         /// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
+        /// <exception cref="SshConnectionException">Client is not connected.</exception>
         public void Download(string filename, FileInfo fileInfo)
         {
             if (string.IsNullOrEmpty(filename))
@@ -383,6 +404,11 @@ namespace Renci.SshNet
                 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 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="directoryInfo"/> is <see langword="null"/>.</exception>
         /// <exception cref="ScpException">File or directory with the specified path does not exist on the remote host.</exception>
         /// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
+        /// <exception cref="SshConnectionException">Client is not connected.</exception>
         public void Download(string directoryName, DirectoryInfo directoryInfo)
         {
             if (string.IsNullOrEmpty(directoryName))
@@ -423,6 +450,11 @@ namespace Renci.SshNet
                 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 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="destination"/> is <see langword="null"/>.</exception>
         /// <exception cref="ScpException"><paramref name="filename"/> exists on the remote host, and is not a regular file.</exception>
         /// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
+        /// <exception cref="SshConnectionException">Client is not connected.</exception>
         public void Download(string filename, Stream destination)
         {
             if (string.IsNullOrWhiteSpace(filename))
@@ -463,6 +496,11 @@ namespace Renci.SshNet
                 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 @@ namespace Renci.SshNet
 
                     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 @@ namespace Renci.SshNet
                     else
                     {
                         // Don't create directory for first level
-                        newDirectoryInfo = fileSystemInfo as DirectoryInfo;
+                        newDirectoryInfo = (DirectoryInfo)fileSystemInfo;
                     }
 
                     directoryCounter++;

+ 6 - 5
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 @@ namespace Renci.SshNet
         /// </value>
         private bool _isDisposed;
 
-        private MemoryStream _inputStream;
+        private MemoryStream? _inputStream;
 
         /// <summary>
         /// Gets the list of forwarded ports.
@@ -272,7 +273,7 @@ namespace Renci.SshNet
         /// Returns a representation of a <see cref="Shell" /> object.
         /// </returns>
         /// <exception cref="SshConnectionException">Client is not connected.</exception>
-        public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModes, int bufferSize)
+        public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint>? terminalModes, int bufferSize)
         {
             EnsureSessionIsOpen();
 
@@ -333,7 +334,7 @@ namespace Renci.SshNet
         /// Returns a representation of a <see cref="Shell" /> object.
         /// </returns>
         /// <exception cref="SshConnectionException">Client is not connected.</exception>
-        public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> 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, uint>? terminalModes, int bufferSize)
         {
             /*
              * TODO Issue #1224: let shell dispose of input stream when we own the stream!
@@ -442,7 +443,7 @@ namespace Renci.SshNet
         /// to the drawable area of the window.
         /// </para>
         /// </remarks>
-        public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, IDictionary<TerminalModes, uint> terminalModeValues)
+        public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, IDictionary<TerminalModes, uint>? terminalModeValues)
         {
             EnsureSessionIsOpen();