浏览代码

Add PipeStreamTest
Add SuppressMessage to some methods
Fix broken Test_Connect_Then_Reconnect test

olegkap_cp 14 年之前
父节点
当前提交
efa9d91f19

+ 61 - 0
Renci.SshClient/Renci.SshNet.Tests/Common/PipeStreamTest.cs

@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Common
+{
+    [TestClass]
+    public class PipeStreamTest
+    {
+        [TestMethod]
+        [TestCategory("PipeStream")]
+        public void Test_PipeStream_Write_Read_Buffer()
+        {
+            var testBuffer = new byte[1024];
+            new Random().NextBytes(testBuffer);
+
+            var outputBuffer = new byte[1024];
+
+            using (var stream = new PipeStream())
+            {
+                stream.Write(testBuffer, 0, testBuffer.Length);
+
+                Assert.AreEqual(stream.Length, testBuffer.Length);
+
+                stream.Read(outputBuffer, 0, outputBuffer.Length);
+
+                Assert.AreEqual(stream.Length, 0);
+
+                Assert.IsTrue(testBuffer.IsEqualTo(outputBuffer));
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("PipeStream")]
+        public void Test_PipeStream_Write_Read_Byte()
+        {
+            var testBuffer = new byte[1024];
+            new Random().NextBytes(testBuffer);
+
+            var outputBuffer = new byte[1024];
+
+            using (var stream = new PipeStream())
+            {
+                stream.Write(testBuffer, 0, testBuffer.Length);
+
+                Assert.AreEqual(stream.Length, testBuffer.Length);
+
+                stream.ReadByte();
+
+                Assert.AreEqual(stream.Length, testBuffer.Length - 1);
+
+                stream.ReadByte();
+
+                Assert.AreEqual(stream.Length, testBuffer.Length - 2);
+            }
+        }
+    }
+}

+ 1 - 0
Renci.SshClient/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

@@ -53,6 +53,7 @@
     </CodeAnalysisDependentAssemblyPaths>
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Common\PipeStreamTest.cs" />
     <Compile Include="ConnectionTest.cs" />
     <Compile Include="Properties\Resources.Designer.cs">
       <DependentUpon>Resources.resx</DependentUpon>

+ 3 - 2
Renci.SshClient/Renci.SshNet.Tests/ScpClientTests/UploadDownloadTest.cs

@@ -7,6 +7,7 @@ using Renci.SshNet.Tests.Properties;
 using System.IO;
 using System.Security.Cryptography;
 using System.Threading.Tasks;
+using Renci.SshNet.Common;
 
 namespace Renci.SshNet.Tests.ScpClientTests
 {
@@ -259,12 +260,12 @@ namespace Renci.SshNet.Tests.ScpClientTests
                 var uploadedFiles = uploadFilenames.ToDictionary((filename) => Path.GetFileName(filename), (filename) => 0L);
                 var downloadedFiles = uploadFilenames.ToDictionary((filename) => string.Format("{0}.down", Path.GetFileName(filename)), (filename) => 0L);
 
-                scp.Uploading += delegate(object sender, Common.ScpUploadEventArgs e)
+                scp.Uploading += delegate(object sender, ScpUploadEventArgs e)
                 {
                     uploadedFiles[e.Filename] = e.Uploaded;
                 };
 
-                scp.Downloading += delegate(object sender, Common.ScpDownloadEventArgs e)
+                scp.Downloading += delegate(object sender, ScpDownloadEventArgs e)
                 {
                     downloadedFiles[string.Format("{0}.down", e.Filename)] = e.Downloaded;
                 };

+ 1 - 1
Renci.SshClient/Renci.SshNet/BaseClient.cs

@@ -123,7 +123,7 @@ namespace Renci.SshNet
 
             this.OnDisconnecting();
 
-            this.Dispose();
+            this.Session.Disconnect();
 
             this.OnDisconnected();
         }

+ 1 - 1
Renci.SshClient/Renci.SshNet/Channels/Channel.cs

@@ -216,7 +216,7 @@ namespace Renci.SshNet.Channels
             //  Wait for channel to be closed
             this._session.WaitHandle(this._channelClosedWaitHandle);
 
-            this.Dispose();
+            //this.Dispose();
         }
 
         #region Channel virtual methods

+ 11 - 0
Renci.SshClient/Renci.SshNet/NetConfClient.cs

@@ -9,6 +9,7 @@ using System.Globalization;
 using System.Threading;
 using Renci.SshNet.NetConf;
 using System.Xml;
+using System.Diagnostics.CodeAnalysis;
 
 namespace Renci.SshNet
 {
@@ -22,6 +23,8 @@ namespace Renci.SshNet
         /// </summary>
         private NetConfSession _netConfSession;
 
+        private bool _disposeConnectionInfo;
+
         /// <summary>
         /// Gets or sets the operation timeout.
         /// </summary>
@@ -52,9 +55,11 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is null or contains whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
+        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
         public NetConfClient(string host, int port, string username, string password)
             : this(new PasswordConnectionInfo(host, port, username, password))
         {
+            this._disposeConnectionInfo = true;
         }
 
         /// <summary>
@@ -80,9 +85,11 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is null.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is null or contains whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
+        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
         public NetConfClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
             : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles))
         {
+            this._disposeConnectionInfo = true;
         }
 
         /// <summary>
@@ -185,6 +192,10 @@ namespace Renci.SshNet
             }
 
             base.Dispose(disposing);
+
+            if (this._disposeConnectionInfo)
+                ((IDisposable)this.ConnectionInfo).Dispose();
+
         }
     }
 }

+ 63 - 3
Renci.SshClient/Renci.SshNet/PrivateKeyFile.cs

@@ -13,19 +13,22 @@ using Renci.SshNet.Security.Cryptography;
 using Renci.SshNet.Security.Cryptography.Ciphers;
 using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
 using Renci.SshNet.Security.Cryptography.Ciphers.Paddings;
+using System.Diagnostics.CodeAnalysis;
 
 namespace Renci.SshNet
 {
     /// <summary>
     /// old private key information/
     /// </summary>
-    public class PrivateKeyFile
+    public class PrivateKeyFile : IDisposable
     {
 #if SILVERLIGHT
         private static Regex _privateKeyRegex = new Regex(@"^-----BEGIN (?<keyName>\w+) PRIVATE KEY-----\r?\n(Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[A-F0-9]+)\r?\n\r?\n)?(?<data>([a-zA-Z0-9/+=]{1,64}\r?\n)+)-----END \k<keyName> PRIVATE KEY-----.*", RegexOptions.Multiline);
 #else
         private static Regex _privateKeyRegex = new Regex(@"^-----BEGIN (?<keyName>\w+) PRIVATE KEY-----\r?\n(Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[A-F0-9]+)\r?\n\r?\n)?(?<data>([a-zA-Z0-9/+=]{1,64}\r?\n)+)-----END \k<keyName> PRIVATE KEY-----.*", RegexOptions.Compiled | RegexOptions.Multiline);
 #endif
+        
+        private Key _key;
 
         /// <summary>
         /// Gets the host key.
@@ -92,6 +95,7 @@ namespace Renci.SshNet
         /// </summary>
         /// <param name="privateKey">The private key.</param>
         /// <param name="passPhrase">The pass phrase.</param>
+        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "this._key disposed in Dispose(bool) method.")]
         private void Open(Stream privateKey, string passPhrase)
         {
             if (privateKey == null)
@@ -164,10 +168,12 @@ namespace Renci.SshNet
             switch (keyName)
             {
                 case "RSA":
-                    this.HostKey = new KeyHostAlgorithm("ssh-rsa", new RsaKey(decryptedData.ToArray()));
+                    this._key = new RsaKey(decryptedData.ToArray());
+                    this.HostKey = new KeyHostAlgorithm("ssh-rsa", this._key);
                     break;
                 case "DSA":
-                    this.HostKey = new KeyHostAlgorithm("ssh-dss", new DsaKey(decryptedData.ToArray()));
+                    this._key = new DsaKey(decryptedData.ToArray());
+                    this.HostKey = new KeyHostAlgorithm("ssh-dss", this._key);
                     break;
                 default:
                     throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Key '{0}' is not supported.", keyName));
@@ -220,5 +226,59 @@ namespace Renci.SshNet
 
             return cipher.Decrypt(cipherData);
         }
+
+        #region IDisposable Members
+
+        private bool _isDisposed = false;
+
+        /// <summary>
+        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged ResourceMessages.
+        /// </summary>
+        public void Dispose()
+        {
+            Dispose(true);
+
+            GC.SuppressFinalize(this);
+        }
+
+        /// <summary>
+        /// Releases unmanaged and - optionally - managed resources
+        /// </summary>
+        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>
+        protected virtual void Dispose(bool disposing)
+        {
+            // Check to see if Dispose has already been called.
+            if (!this._isDisposed)
+            {
+                // If disposing equals true, dispose all managed
+                // and unmanaged ResourceMessages.
+                if (disposing)
+                {
+                    // Dispose managed ResourceMessages.
+                    if (this._key != null)
+                    {
+                        ((IDisposable)this._key).Dispose();
+                        this._key = null;
+                    }
+                }
+
+                // Note disposing has been done.
+                _isDisposed = true;
+            }
+        }
+
+        /// <summary>
+        /// Releases unmanaged resources and performs other cleanup operations before the
+        /// <see cref="BaseClient"/> is reclaimed by garbage collection.
+        /// </summary>
+        ~PrivateKeyFile()
+        {
+            // Do not re-create Dispose clean-up code here.
+            // Calling Dispose(false) is optimal in terms of
+            // readability and maintainability.
+            Dispose(false);
+        }
+
+        #endregion
     }
 }

+ 19 - 0
Renci.SshClient/Renci.SshNet/ScpClient.cs

@@ -9,6 +9,7 @@ using Renci.SshNet.Messages.Connection;
 using System.Text.RegularExpressions;
 using System.Threading;
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 
 namespace Renci.SshNet
 {
@@ -25,6 +26,8 @@ namespace Renci.SshNet
 
         private static char[] _byteToChar;
 
+        private bool _disposeConnectionInfo;
+
         /// <summary>
         /// Gets or sets the operation timeout.
         /// </summary>
@@ -82,9 +85,11 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is null or contains whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
+        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
         public ScpClient(string host, int port, string username, string password)
             : this(new PasswordConnectionInfo(host, port, username, password))
         {
+            this._disposeConnectionInfo = true;
         }
 
         /// <summary>
@@ -110,9 +115,11 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is null.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is null or contains whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
+        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
         public ScpClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
             : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles))
         {
+            this._disposeConnectionInfo = true;
         }
 
         /// <summary>
@@ -372,5 +379,17 @@ namespace Renci.SshNet
 
             return sb.ToString();
         }
+
+        /// <summary>
+        /// Releases unmanaged and - optionally - managed resources
+        /// </summary>
+        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>
+        protected override void Dispose(bool disposing)
+        {
+            base.Dispose(disposing);
+
+            if (this._disposeConnectionInfo)
+                ((IDisposable)this.ConnectionInfo).Dispose();
+        }
     }
 }

+ 1 - 1
Renci.SshClient/Renci.SshNet/Session.cs

@@ -580,7 +580,7 @@ namespace Renci.SshNet
             //  If socket still open try to send disconnect message to the server
             this.SendDisconnect(DisconnectReason.ByApplication, "Connection terminated by the client.");
 
-            this.Dispose();
+            //this.Dispose();
         }
 
         internal T CreateChannel<T>() where T : Channel, new()

+ 3 - 2
Renci.SshClient/Renci.SshNet/Sftp/SftpFileStream.cs

@@ -4,6 +4,7 @@ using System.Linq;
 using System.Text;
 using System.IO;
 using System.Threading;
+using System.Diagnostics.CodeAnalysis;
 
 namespace Renci.SshNet.Sftp
 {
@@ -74,10 +75,10 @@ namespace Renci.SshNet.Sftp
         /// Gets the length in bytes of the stream.
         /// </summary>
         /// <returns>A long value representing the length of the stream in bytes.</returns>
-        ///   
         /// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </exception>
-        ///   
         /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
+        /// <exception cref="T:System.IO.IOException">IO operation failed. </exception>
+        [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Justification = "Be design this is the exception that stream need to throw.")]
         public override long Length
         {
             get

+ 10 - 0
Renci.SshClient/Renci.SshNet/SftpClient.cs

@@ -7,6 +7,7 @@ using System.Text;
 using Renci.SshNet.Common;
 using System.Globalization;
 using System.Threading;
+using System.Diagnostics.CodeAnalysis;
 
 namespace Renci.SshNet
 {
@@ -20,6 +21,8 @@ namespace Renci.SshNet
         /// </summary>
         private SftpSession _sftpSession;
 
+        private bool _disposeConnectionInfo;
+
         /// <summary>
         /// Gets or sets the operation timeout.
         /// </summary>
@@ -74,9 +77,11 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is null or contains whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
+        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
         public SftpClient(string host, int port, string username, string password)
             : this(new PasswordConnectionInfo(host, port, username, password))
         {
+            this._disposeConnectionInfo = true;
         }
 
         /// <summary>
@@ -102,9 +107,11 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is null.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is null or contains whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
+        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
         public SftpClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
             : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles))
         {
+            this._disposeConnectionInfo = true;
         }
 
         /// <summary>
@@ -1323,6 +1330,9 @@ namespace Renci.SshNet
                 this._sftpSession = null;
             }
 
+            if (this._disposeConnectionInfo)
+                ((IDisposable)this.ConnectionInfo).Dispose();
+
             base.Dispose(disposing);
         }
     }

+ 30 - 4
Renci.SshClient/Renci.SshNet/SshClient.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Collections.ObjectModel;
 using System.Text;
+using System.Diagnostics.CodeAnalysis;
 
 namespace Renci.SshNet
 {
@@ -16,6 +17,10 @@ namespace Renci.SshNet
         /// </summary>
         private List<ForwardedPort> _forwardedPorts = new List<ForwardedPort>();
 
+        private bool _disposeConnectionInfo;
+        
+        private Stream _inputStream;
+
         /// <summary>
         /// Gets the list of forwarded ports.
         /// </summary>
@@ -49,9 +54,11 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is null or contains whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
+        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification="Disposed in Dispose(bool) method.")]
         public SshClient(string host, int port, string username, string password)
             : this(new PasswordConnectionInfo(host, port, username, password))
         {
+            this._disposeConnectionInfo = true;
         }
 
         /// <summary>
@@ -77,9 +84,11 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is null.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is null or contains whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
+        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
         public SshClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
             : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles))
         {
+            this._disposeConnectionInfo = true;
         }
 
         /// <summary>
@@ -306,13 +315,13 @@ namespace Renci.SshNet
             //  Ensure that connection is established.
             this.EnsureConnection();
 
-            var inputStream = new MemoryStream();
-            var writer = new StreamWriter(inputStream, encoding);
+            this._inputStream = new MemoryStream();
+            var writer = new StreamWriter(this._inputStream, encoding);
             writer.Write(input);
             writer.Flush();
-            inputStream.Seek(0, SeekOrigin.Begin);
+            this._inputStream.Seek(0, SeekOrigin.Begin);
 
-            return this.CreateShell(inputStream, output, extendedOutput, terminalName, columns, rows, width, height, terminalMode, bufferSize);
+            return this.CreateShell(this._inputStream, output, extendedOutput, terminalName, columns, rows, width, height, terminalMode, bufferSize);
         }
 
         /// <summary>
@@ -347,5 +356,22 @@ namespace Renci.SshNet
             return this.CreateShell(encoding, input, output, extendedOutput, string.Empty, 0, 0, 0, 0, string.Empty, 1024);
         }
 
+        /// <summary>
+        /// Releases unmanaged and - optionally - managed resources
+        /// </summary>
+        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>
+        protected override void Dispose(bool disposing)
+        {
+            base.Dispose(disposing);
+
+            if (this._disposeConnectionInfo)
+                ((IDisposable)this.ConnectionInfo).Dispose();
+
+            if (this._inputStream != null)
+            {
+                this._inputStream.Dispose();
+                this._inputStream = null;
+            }
+        }
     }
 }