Bladeren bron

Minor fix to ASCIIEncoding
Add ScpClient to support SCP feature with limited featres supported in silverlight and WindowsPhone environment
Add ScpClient test

olegkap_cp 14 jaren geleden
bovenliggende
commit
d96c93abb2

+ 18 - 0
Renci.SshClient/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj

@@ -133,6 +133,18 @@
     <Compile Include="..\Renci.SshNet\Common\PortForwardEventArgs.cs">
       <Link>Common\PortForwardEventArgs.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Common\ScpDownloadEventArgs.cs">
+      <Link>Common\ScpDownloadEventArgs.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Common\ScpException.cs">
+      <Link>Common\ScpException.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Common\ScpException.NET40.cs">
+      <Link>Common\ScpException.NET40.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Common\ScpUploadEventArgs.cs">
+      <Link>Common\ScpUploadEventArgs.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Common\SemaphoreLight.cs">
       <Link>Common\SemaphoreLight.cs</Link>
     </Compile>
@@ -436,6 +448,12 @@
     <Compile Include="..\Renci.SshNet\PrivateKeyFile.cs">
       <Link>PrivateKeyFile.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\ScpClient.cs">
+      <Link>ScpClient.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\ScpClient.NET.cs">
+      <Link>ScpClient.NET.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Security\Algorithm.cs">
       <Link>Security\Algorithm.cs</Link>
     </Compile>

+ 14 - 1
Renci.SshClient/Renci.SshNet.Silverlight/Renci.SshNet.Silverlight.csproj

@@ -141,6 +141,15 @@
     <Compile Include="..\Renci.SshNet\Common\PortForwardEventArgs.cs">
       <Link>Common\PortForwardEventArgs.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Common\ScpDownloadEventArgs.cs">
+      <Link>Common\ScpDownloadEventArgs.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Common\ScpException.cs">
+      <Link>Common\ScpException.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Common\ScpUploadEventArgs.cs">
+      <Link>Common\ScpUploadEventArgs.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Common\SemaphoreLight.cs">
       <Link>Common\SemaphoreLight.cs</Link>
     </Compile>
@@ -417,6 +426,9 @@
     <Compile Include="..\Renci.SshNet\PrivateKeyFile.cs">
       <Link>PrivateKeyFile.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\ScpClient.cs">
+      <Link>ScpClient.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Security\Algorithm.cs">
       <Link>Security\Algorithm.cs</Link>
     </Compile>
@@ -686,6 +698,7 @@
     <Compile Include="ForwardedPortRemote.SilverlightShared.cs" />
     <Compile Include="PasswordConnectionInfo.SilverlightShared.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ScpClient.SilverlightShared.cs" />
     <Compile Include="Session.SilverlightBrowser.cs" />
     <Compile Include="Session.SilverlightShared.cs" />
     <Compile Include="SftpClient.SilverlightShared.cs" />
@@ -697,7 +710,7 @@
       <FlavorProperties GUID="{A1591282-1198-4647-A2B1-27E5FF5F6F3B}">
         <SilverlightProjectProperties />
       </FlavorProperties>
-      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
+      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 25 - 0
Renci.SshClient/Renci.SshNet.Silverlight/ScpClient.SilverlightShared.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Renci.SshNet.Channels;
+using System.IO;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Diagnostics;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// Provides SCP client functionality.
+    /// </summary>
+    public partial class ScpClient
+    {
+        partial void SendData(ChannelSession channel, string command)
+        {
+            this.Session.SendMessage(new ChannelDataMessage(channel.RemoteChannelNumber, System.Text.Encoding.UTF8.GetBytes(command)));
+        }
+    }
+}

+ 4 - 1
Renci.SshClient/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj

@@ -53,6 +53,9 @@
     <Compile Include="..\Renci.SshNet.Tests\ConnectionTest.cs">
       <Link>ConnectionTest.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\ScpClientTests\UploadDownloadTest.cs">
+      <Link>ScpClientTests\UploadDownloadTest.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Security\Cryptography\Ciphers.cs">
       <Link>Security\Cryptography\Ciphers.cs</Link>
     </Compile>
@@ -134,7 +137,7 @@
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" />
+      <UserProperties ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

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

@@ -59,6 +59,9 @@
       <DesignTime>True</DesignTime>
       <AutoGen>True</AutoGen>
     </Compile>
+    <Compile Include="ScpClientTests\UploadDownloadTest.cs">
+      <SubType>Code</SubType>
+    </Compile>
     <Compile Include="Security\Cryptography\Ciphers.cs" />
     <Compile Include="Security\TestCipher.cs" />
     <Compile Include="Security\TestHMac.cs" />

+ 336 - 0
Renci.SshClient/Renci.SshNet.Tests/ScpClientTests/UploadDownloadTest.cs

@@ -0,0 +1,336 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Tests.Properties;
+using System.IO;
+using System.Security.Cryptography;
+using System.Threading.Tasks;
+
+namespace Renci.SshNet.Tests.ScpClientTests
+{
+    [TestClass]
+    public class UploadDownloadTest
+    {
+        [TestInitialize()]
+        public void CleanCurrentFolder()
+        {
+            using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            {
+                client.Connect();
+                client.RunCommand("rm -rf *");
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_File_Upload_Download()
+        {
+            using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            {
+                scp.Connect();
+
+                string uploadedFileName = Path.GetTempFileName();
+                string downloadedFileName = Path.GetTempFileName();
+
+                this.CreateTestFile(uploadedFileName, 1);
+
+                scp.Upload(new FileInfo(uploadedFileName), Path.GetFileName(uploadedFileName));
+
+                scp.Download(Path.GetFileName(uploadedFileName), new FileInfo(downloadedFileName));
+
+                //  Calculate MD5 value
+                var uploadedHash = CalculateMD5(uploadedFileName);
+                var downloadedHash = CalculateMD5(downloadedFileName);
+
+                File.Delete(uploadedFileName);
+                File.Delete(downloadedFileName);
+
+                scp.Disconnect();
+
+                Assert.AreEqual(uploadedHash, downloadedHash);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_Stream_Upload_Download()
+        {
+            using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            {
+                scp.Connect();
+
+                string uploadedFileName = Path.GetTempFileName();
+                string downloadedFileName = Path.GetTempFileName();
+
+                this.CreateTestFile(uploadedFileName, 1);
+
+                //  Calculate has value
+                using (var stream = File.OpenRead(uploadedFileName))
+                {
+                    scp.Upload(stream, Path.GetFileName(uploadedFileName));
+                }
+
+                using (var stream = File.OpenWrite(downloadedFileName))
+                {
+                    scp.Download(Path.GetFileName(uploadedFileName), stream);
+                }
+
+                //  Calculate MD5 value
+                var uploadedHash = CalculateMD5(uploadedFileName);
+                var downloadedHash = CalculateMD5(downloadedFileName);
+
+                File.Delete(uploadedFileName);
+                File.Delete(downloadedFileName);
+
+                scp.Disconnect();
+
+                Assert.AreEqual(uploadedHash, downloadedHash);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_10MB_File_Upload_Download()
+        {
+            using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            {
+                scp.Connect();
+
+                string uploadedFileName = Path.GetTempFileName();
+                string downloadedFileName = Path.GetTempFileName();
+
+                this.CreateTestFile(uploadedFileName, 10);
+
+                scp.Upload(new FileInfo(uploadedFileName), Path.GetFileName(uploadedFileName));
+
+                scp.Download(Path.GetFileName(uploadedFileName), new FileInfo(downloadedFileName));
+
+                //  Calculate MD5 value
+                var uploadedHash = CalculateMD5(uploadedFileName);
+                var downloadedHash = CalculateMD5(downloadedFileName);
+
+                File.Delete(uploadedFileName);
+                File.Delete(downloadedFileName);
+
+                scp.Disconnect();
+
+                Assert.AreEqual(uploadedHash, downloadedHash);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_10MB_Stream_Upload_Download()
+        {
+            using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            {
+                scp.Connect();
+
+                string uploadedFileName = Path.GetTempFileName();
+                string downloadedFileName = Path.GetTempFileName();
+
+                this.CreateTestFile(uploadedFileName, 10);
+
+                //  Calculate has value
+                using (var stream = File.OpenRead(uploadedFileName))
+                {
+                    scp.Upload(stream, Path.GetFileName(uploadedFileName));
+                }
+
+                using (var stream = File.OpenWrite(downloadedFileName))
+                {
+                    scp.Download(Path.GetFileName(uploadedFileName), stream);
+                }
+
+                //  Calculate MD5 value
+                var uploadedHash = CalculateMD5(uploadedFileName);
+                var downloadedHash = CalculateMD5(downloadedFileName);
+
+                File.Delete(uploadedFileName);
+                File.Delete(downloadedFileName);
+
+                scp.Disconnect();
+
+                Assert.AreEqual(uploadedHash, downloadedHash);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_Directory_Upload_Download()
+        {
+            using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            {
+                scp.Connect();
+
+                var uploadDirectory = Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName()));
+                for (int i = 0; i < 3; i++)
+                {
+                    var subfolder = Directory.CreateDirectory(string.Format(@"{0}\folder_{1}", uploadDirectory.FullName, i));
+                    for (int j = 0; j < 5; j++)
+                    {
+                        this.CreateTestFile(string.Format(@"{0}\file_{1}", subfolder.FullName, j), 1);
+                    }
+                    this.CreateTestFile(string.Format(@"{0}\file_{1}", uploadDirectory.FullName, i), 1);
+                }
+
+                scp.Upload(uploadDirectory, "uploaded_dir");
+
+                var downloadDirectory = Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName()));
+
+                scp.Download("uploaded_dir", downloadDirectory);
+
+                var uploadedFiles = uploadDirectory.GetFiles("*.*", System.IO.SearchOption.AllDirectories);
+                var downloadFiles = downloadDirectory.GetFiles("*.*", System.IO.SearchOption.AllDirectories);
+
+                var result = from f1 in uploadedFiles
+                             from f2 in downloadFiles
+                             where
+                                f1.FullName.Substring(uploadDirectory.FullName.Length) == f2.FullName.Substring(downloadDirectory.FullName.Length)
+                                && CalculateMD5(f1.FullName) == CalculateMD5(f2.FullName)
+                             select f1;
+
+                var counter = result.Count();
+
+                scp.Disconnect();
+
+                Assert.IsTrue(counter == uploadedFiles.Length && uploadedFiles.Length == downloadFiles.Length);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_File_20_Parallel_Upload_Download()
+        {
+            using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            {
+                scp.Connect();
+
+                var uploadFilenames = new string[20];
+                for (int i = 0; i < uploadFilenames.Length; i++)
+                {
+                    uploadFilenames[i] = Path.GetTempFileName();
+                    this.CreateTestFile(uploadFilenames[i], 1);
+                }
+
+                Parallel.ForEach(uploadFilenames,
+                    (filename) =>
+                    {
+                        scp.Upload(new FileInfo(filename), Path.GetFileName(filename));
+                    });
+
+                Parallel.ForEach(uploadFilenames,
+                    (filename) =>
+                    {
+                        scp.Download(Path.GetFileName(filename), new FileInfo(string.Format("{0}.down", filename)));
+                    });
+
+                var result = from file in uploadFilenames
+                             where
+                                 CalculateMD5(file) == CalculateMD5(string.Format("{0}.down", file))
+                             select file;
+
+
+                scp.Disconnect();
+
+                Assert.IsTrue(result.Count() == uploadFilenames.Length);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_File_Upload_Download_Events()
+        {
+            using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            {
+                scp.Connect();
+
+                var uploadFilenames = new string[10];
+
+                for (int i = 0; i < uploadFilenames.Length; i++)
+                {
+                    uploadFilenames[i] = Path.GetTempFileName();
+                    this.CreateTestFile(uploadFilenames[i], 1);
+                }
+
+                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)
+                {
+                    uploadedFiles[e.Filename] = e.Uploaded;
+                };
+
+                scp.Downloading += delegate(object sender, Common.ScpDownloadEventArgs e)
+                {
+                    downloadedFiles[string.Format("{0}.down", e.Filename)] = e.Downloaded;
+                };
+
+
+                Parallel.ForEach(uploadFilenames,
+                    (filename) =>
+                    {
+                        scp.Upload(new FileInfo(filename), Path.GetFileName(filename));
+                    });
+
+                Parallel.ForEach(uploadFilenames,
+                    (filename) =>
+                    {
+                        scp.Download(Path.GetFileName(filename), new FileInfo(string.Format("{0}.down", filename)));
+                    });
+
+                var result = from uf in uploadedFiles
+                             from df in downloadedFiles
+                             where
+                                 string.Format("{0}.down", uf.Key) == df.Key
+                                 && uf.Value == df.Value
+                             select uf;
+
+
+                scp.Disconnect();
+
+                Assert.IsTrue(result.Count() == uploadFilenames.Length && uploadFilenames.Length == uploadedFiles.Count && uploadedFiles.Count == downloadedFiles.Count);
+            }
+        }
+
+        /// <summary>
+        /// Creates the test file.
+        /// </summary>
+        /// <param name="fileName">Name of the file.</param>
+        /// <param name="size">Size in megabytes.</param>
+        private void CreateTestFile(string fileName, int size)
+        {
+            using (var testFile = File.Create(fileName))
+            {
+
+                var random = new Random();
+                for (int i = 0; i < 1024 * size; i++)
+                {
+                    var buffer = new byte[1024];
+                    random.NextBytes(buffer);
+                    testFile.Write(buffer, 0, buffer.Length);
+                }
+            }
+        }
+
+        protected static string CalculateMD5(string fileName)
+        {
+            using (FileStream file = new FileStream(fileName, FileMode.Open))
+            {
+                var md5 = new MD5CryptoServiceProvider();
+                byte[] retVal = md5.ComputeHash(file);
+                file.Close();
+
+                StringBuilder sb = new StringBuilder();
+                for (int i = 0; i < retVal.Length; i++)
+                {
+                    sb.Append(retVal[i].ToString("x2"));
+                }
+                return sb.ToString();
+            }
+        }
+    }
+}

+ 16 - 1
Renci.SshClient/Renci.SshNet.WindowsPhone/Renci.SshNet.WindowsPhone.csproj

@@ -137,6 +137,15 @@
     <Compile Include="..\Renci.SshNet\Common\PortForwardEventArgs.cs">
       <Link>Common\PortForwardEventArgs.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Common\ScpDownloadEventArgs.cs">
+      <Link>Common\ScpDownloadEventArgs.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Common\ScpException.cs">
+      <Link>Common\ScpException.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Common\ScpUploadEventArgs.cs">
+      <Link>Common\ScpUploadEventArgs.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Common\SemaphoreLight.cs">
       <Link>Common\SemaphoreLight.cs</Link>
     </Compile>
@@ -416,6 +425,12 @@
     <Compile Include="..\Renci.SshNet\PrivateKeyFile.cs">
       <Link>PrivateKeyFile.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\ScpClient.cs">
+      <Link>ScpClient.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Silverlight\ScpClient.SilverlightShared.cs">
+      <Link>ScpClient.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Security\Algorithm.cs">
       <Link>Security\Algorithm.cs</Link>
     </Compile>
@@ -699,7 +714,7 @@
   <Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\$(TargetFrameworkVersion)\Microsoft.Silverlight.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
+      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 17 - 2
Renci.SshClient/Renci.SshNet/Common/ASCIIEncoding.cs

@@ -12,6 +12,21 @@ namespace Renci.SshNet.Common
     {
         private readonly char _fallbackChar;
 
+        private static char[] _byteToChar;
+
+        static ASCIIEncoding()
+        {
+            if (_byteToChar == null)
+            {
+                _byteToChar = new char[128];
+                var ch = '\0';
+                for (byte i = 0; i < 128; i++)
+                {
+                    _byteToChar[i] = ch++;
+                }
+            }
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ASCIIEncoding"/> class.
         /// </summary>
@@ -112,7 +127,7 @@ namespace Renci.SshNet.Common
         ///   <paramref name="bytes"/> is null.-or- <paramref name="chars"/> is null. </exception>
         ///   
         /// <exception cref="T:System.ArgumentOutOfRangeException">
-        ///   <paramref name="byteIndex"/> or <paramref name="byteCount"/> or <paramref name="charIndex"/> is less than zero.-or- <paramref name="byteindex"/> and <paramref name="byteCount"/> do not denote a valid range in <paramref name="bytes"/>.-or- <paramref name="charIndex"/> is not a valid index in <paramref name="chars"/>. </exception>
+        ///   <paramref name="byteIndex"/> or <paramref name="byteCount"/> or <paramref name="charIndex"/> is less than zero.-or- <paramref name="byteIndex"/> and <paramref name="byteCount"/> do not denote a valid range in <paramref name="bytes"/>.-or- <paramref name="charIndex"/> is not a valid index in <paramref name="chars"/>. </exception>
         ///   
         /// <exception cref="T:System.ArgumentException">
         ///   <paramref name="chars"/> does not have enough capacity from <paramref name="charIndex"/> to the end of the array to accommodate the resulting characters. </exception>
@@ -131,7 +146,7 @@ namespace Renci.SshNet.Common
                 }
                 else 
                 {
-                    ch = (char)b;
+                    ch = _byteToChar[b];
                 }
 
                 chars[i + charIndex] = ch;

+ 19 - 0
Renci.SshClient/Renci.SshNet/Common/HostKeyEventArgs.cs

@@ -6,14 +6,33 @@ using Renci.SshNet.Security.Cryptography;
 
 namespace Renci.SshNet.Common
 {
+    /// <summary>
+    /// Provides data for the HostKeyReceived event.
+    /// </summary>
     public class HostKeyEventArgs : EventArgs
     {
+        /// <summary>
+        /// Gets or sets a value indicating whether host key can be trusted.
+        /// </summary>
+        /// <value>
+        ///   <c>true</c> if host key can be trusted; otherwise, <c>false</c>.
+        /// </value>
         public bool CanTrust { get; set; }
 
+        /// <summary>
+        /// Gets the host key.
+        /// </summary>
         public byte[] HostKey { get; private set; }
 
+        /// <summary>
+        /// Gets the finger print.
+        /// </summary>
         public byte[] FingerPrint { get; private set; }
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="HostKeyEventArgs"/> class.
+        /// </summary>
+        /// <param name="hostKey">The host key.</param>
         public HostKeyEventArgs(byte[] hostKey)
         {
             this.CanTrust = true;   //  Set default value

+ 41 - 0
Renci.SshClient/Renci.SshNet/Common/ScpDownloadEventArgs.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Renci.SshNet.Common
+{
+    /// <summary>
+    /// Provides data for the Downloading event.
+    /// </summary>
+    public class ScpDownloadEventArgs : EventArgs
+    {
+        /// <summary>
+        /// Gets the downloaded filename.
+        /// </summary>
+        public string Filename { get; private set; }
+
+        /// <summary>
+        /// Gets the downloaded file size.
+        /// </summary>
+        public long Size { get; private set; }
+
+        /// <summary>
+        /// Gets number of downloaded bytes so far.
+        /// </summary>
+        public long Downloaded { get; private set; }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ScpDownloadEventArgs"/> class.
+        /// </summary>
+        /// <param name="filename">The downloaded filename.</param>
+        /// <param name="size">The downloaded file size.</param>
+        /// <param name="downloaded">The number of downloaded bytes so far.</param>
+        public ScpDownloadEventArgs(string filename, long size, long downloaded)
+        {
+            this.Filename = filename;
+            this.Size = size;
+            this.Downloaded = downloaded;
+        }
+    }
+}

+ 24 - 0
Renci.SshClient/Renci.SshNet/Common/ScpException.NET40.cs

@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.Serialization;
+
+namespace Renci.SshNet.Common
+{
+    [Serializable]
+    public partial class ScpException : SshException
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SftpPathNotFoundException"/> class.
+        /// </summary>
+        /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
+        /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param>
+        /// <exception cref="T:System.ArgumentNullException">The <paramref name="info"/> parameter is null. </exception>
+        ///   
+        /// <exception cref="T:System.Runtime.Serialization.SerializationException">The class name is null or <see cref="P:System.Exception.HResult"/> is zero (0). </exception>
+        protected ScpException(SerializationInfo info, StreamingContext context)
+            : base(info, context)
+        {
+        }
+    }}

+ 41 - 0
Renci.SshClient/Renci.SshNet/Common/ScpException.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Renci.SshNet.Common
+{
+    /// <summary>
+    /// The exception that is thrown when SCP error occurred.
+    /// </summary>
+    public partial class ScpException : SshException
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ScpException"/> class.
+        /// </summary>
+        public ScpException()
+        {
+
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ScpException"/> class.
+        /// </summary>
+        /// <param name="message">The message.</param>
+        public ScpException(string message)
+            : base(message)
+        {
+
+        }
+
+        /// <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) :
+            base(message, innerException)
+        {
+        }
+    }
+}

+ 41 - 0
Renci.SshClient/Renci.SshNet/Common/ScpUploadEventArgs.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Renci.SshNet.Common
+{
+    /// <summary>
+    /// Provides data for the Uploading event.
+    /// </summary>
+    public class ScpUploadEventArgs : EventArgs
+    {
+        /// <summary>
+        /// Gets the uploaded filename.
+        /// </summary>
+        public string Filename { get; private set; }
+
+        /// <summary>
+        /// Gets the uploaded file size.
+        /// </summary>
+        public long Size { get; private set; }
+
+        /// <summary>
+        /// Gets number of uploaded bytes so far.
+        /// </summary>
+        public long Uploaded { get; private set; }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ScpUploadEventArgs"/> class.
+        /// </summary>
+        /// <param name="filename">The uploaded filename.</param>
+        /// <param name="size">The the uploaded file size.</param>
+        /// <param name="uploaded">The number of uploaded bytes so far.</param>
+        public ScpUploadEventArgs(string filename, long size, long uploaded)
+        {
+            this.Filename = filename;
+            this.Size = size;
+            this.Uploaded = uploaded;
+        }
+    }
+}

+ 10 - 0
Renci.SshClient/Renci.SshNet/Compression/CompressionMode.cs

@@ -1,8 +1,18 @@
 namespace Renci.SshNet.Compression
 {
+    /// <summary>
+    /// Specifies compression modes
+    /// </summary>
     public enum CompressionMode
     {
+        /// <summary>
+        /// Specifies that content should be compressed.
+        /// </summary>
         Compress = 0,
+
+        /// <summary>
+        /// Specifies that content should be decompressed.
+        /// </summary>
         Decompress = 1,
     }
 }

+ 16 - 0
Renci.SshClient/Renci.SshNet/Renci.SshNet.csproj

@@ -85,6 +85,18 @@
     <Compile Include="Common\PortForwardEventArgs.cs">
       <SubType>Code</SubType>
     </Compile>
+    <Compile Include="Common\ScpDownloadEventArgs.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Common\ScpException.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Common\ScpException.NET40.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Common\ScpUploadEventArgs.cs">
+      <SubType>Code</SubType>
+    </Compile>
     <Compile Include="Common\SemaphoreLight.cs">
       <SubType>Code</SubType>
     </Compile>
@@ -113,6 +125,10 @@
     <Compile Include="ConnectionInfo.cs">
       <SubType>Code</SubType>
     </Compile>
+    <Compile Include="ScpClient.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="ScpClient.NET.cs" />
     <Compile Include="ForwardedPort.cs" />
     <Compile Include="ForwardedPortLocal.cs">
       <SubType>Code</SubType>

+ 276 - 0
Renci.SshClient/Renci.SshNet/ScpClient.NET.cs

@@ -0,0 +1,276 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Renci.SshNet.Channels;
+using System.IO;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Diagnostics;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// Provides SCP client functionality.
+    /// </summary>
+    public partial class ScpClient
+    {
+        /// <summary>
+        /// Uploads the specified file to the remote host.
+        /// </summary>
+        /// <param name="fileInfo">Local file to upload.</param>
+        /// <param name="filename">Remote host file name.</param>
+        public void Upload(FileInfo fileInfo, string filename)
+        {
+            using (var input = new PipeStream())
+            using (var channel = this.Session.CreateChannel<ChannelSession>())
+            {
+                channel.DataReceived += delegate(object sender, Common.ChannelDataEventArgs e)
+                {
+                    input.Write(e.Data, 0, e.Data.Length);
+                    input.Flush();
+                };
+
+                channel.Open();
+
+                //  Send channel command request
+                channel.SendExecRequest(string.Format("scp -qt \"{0}\"", filename));
+                this.CheckReturnCode(input);
+
+                this.InternalUpload(channel, input, fileInfo, filename);
+
+                channel.Close();
+            }
+        }
+
+        /// <summary>
+        /// Uploads the specified directory to the remote host.
+        /// </summary>
+        /// <param name="directoryInfo">Local directory to upload.</param>
+        /// <param name="filename">Remote host directory name.</param>
+        public void Upload(DirectoryInfo directoryInfo, string filename)
+        {
+            using (var input = new PipeStream())
+            using (var channel = this.Session.CreateChannel<ChannelSession>())
+            {
+                channel.DataReceived += delegate(object sender, Common.ChannelDataEventArgs e)
+                {
+                    input.Write(e.Data, 0, e.Data.Length);
+                    input.Flush();
+                };
+
+                channel.Open();
+
+                //  Send channel command request
+                channel.SendExecRequest(string.Format("scp -qrt {0}", filename));
+                this.CheckReturnCode(input);
+
+                this.InternalUpload(channel, input, directoryInfo, filename);
+
+                channel.Close();
+            }
+        }
+
+        /// <summary>
+        /// Downloads the specified file from the remote host to local file.
+        /// </summary>
+        /// <param name="filename">Remote host file name.</param>
+        /// <param name="fileInfo">Local file information.</param>
+        public void Download(string filename, FileInfo fileInfo)
+        {
+            using (var input = new PipeStream())
+            using (var channel = this.Session.CreateChannel<ChannelSession>())
+            {
+                channel.DataReceived += delegate(object sender, Common.ChannelDataEventArgs e)
+                {
+                    input.Write(e.Data, 0, e.Data.Length);
+                    input.Flush();
+                };
+
+                channel.Open();
+
+                //  Send channel command request
+                channel.SendExecRequest(string.Format("scp -qpf \"{0}\"", filename));
+                this.SendConfirmation(channel); //  Send reply
+
+                this.InternalDownload(channel, input, fileInfo);
+
+                channel.Close();
+            }
+        }
+
+        /// <summary>
+        /// Downloads the specified directory from the remote host to local directory.
+        /// </summary>
+        /// <param name="directoryName">Remote host directory name.</param>
+        /// <param name="directoryInfo">Local directory information.</param>
+        public void Download(string directoryName, DirectoryInfo directoryInfo)
+        {
+            using (var input = new PipeStream())
+            using (var channel = this.Session.CreateChannel<ChannelSession>())
+            {
+                channel.DataReceived += delegate(object sender, Common.ChannelDataEventArgs e)
+                {
+                    input.Write(e.Data, 0, e.Data.Length);
+                    input.Flush();
+                };
+
+                channel.Open();
+
+                //  Send channel command request
+                channel.SendExecRequest(string.Format("scp -qprf \"{0}\"", directoryName));
+                this.SendConfirmation(channel); //  Send reply
+
+                this.InternalDownload(channel, input, directoryInfo);
+
+                channel.Close();
+            }
+        }
+
+        private void InternalUpload(ChannelSession channel, Stream input, FileInfo fileInfo, string filename)
+        {
+            this.InternalSetTimestamp(channel, input, fileInfo.LastWriteTimeUtc, fileInfo.LastAccessTimeUtc);
+
+            using (var source = fileInfo.OpenRead())
+            {
+                this.InternalFileUpload(channel, input, source, filename);
+            }
+        }
+
+        private void InternalUpload(ChannelSession channel, PipeStream input, DirectoryInfo directoryInfo, string directoryName)
+        {
+            this.InternalSetTimestamp(channel, input, directoryInfo.LastWriteTimeUtc, directoryInfo.LastAccessTimeUtc);
+
+            this.SendData(channel, string.Format("D0755 0 {0}\n", directoryName));
+            this.CheckReturnCode(input);
+
+            //  Upload files
+            var files = directoryInfo.GetFiles();
+            foreach (var file in files)
+            {
+                this.InternalUpload(channel, input, file, file.Name);
+            }
+
+            //  Upload directories
+            var directories = directoryInfo.GetDirectories();
+            foreach (var directory in directories)
+            {
+                this.InternalUpload(channel, input, directory, directory.Name);
+            }
+
+            this.SendData(channel, "E\n");
+            this.CheckReturnCode(input);
+        }
+
+        private void InternalDownload(ChannelSession channel, Stream input, FileSystemInfo fileSystemInfo)
+        {
+            DateTime modifiedTime = DateTime.Now;
+            DateTime accessedTime = DateTime.Now;
+
+            var startDirectoryFullName = fileSystemInfo.FullName;
+            var currentDirectoryFullName = startDirectoryFullName;
+            var directoryCounter = 0;
+
+            while (true)
+            {
+                var message = ReadString(input);
+
+                if (message == "E")
+                {
+                    this.SendConfirmation(channel); //  Send reply
+
+                    directoryCounter--;
+
+                    currentDirectoryFullName = new DirectoryInfo(currentDirectoryFullName).Parent.FullName;
+
+                    //if (currentDirectoryFullName == startDirectoryFullName)
+                    if (directoryCounter == 0)
+                        break;
+                    else
+                        continue;
+                }
+
+                var match = _directoryInfoRe.Match(message);
+                if (match.Success)
+                {
+                    this.SendConfirmation(channel); //  Send reply
+
+                    //  Read directory
+                    var mode = long.Parse(match.Result("${mode}"));
+                    var filename = match.Result("${filename}");
+
+                    DirectoryInfo newDirectoryInfo;
+                    if (directoryCounter > 0)
+                    {
+                        newDirectoryInfo = Directory.CreateDirectory(string.Format("{0}{1}{2}", currentDirectoryFullName, Path.DirectorySeparatorChar, filename));
+                        newDirectoryInfo.LastAccessTime = accessedTime;
+                        newDirectoryInfo.LastWriteTime = modifiedTime;
+                    }
+                    else
+                    {
+                        //  Dont create directory for first level
+                        newDirectoryInfo = fileSystemInfo as DirectoryInfo;
+                    }
+
+                    directoryCounter++;
+
+                    currentDirectoryFullName = newDirectoryInfo.FullName;
+                    continue;
+                }
+
+                match = _fileInfoRe.Match(message);
+                if (match.Success)
+                {
+                    //  Read file
+                    this.SendConfirmation(channel); //  Send reply
+
+                    var mode = match.Result("${mode}");
+                    var length = long.Parse(match.Result("${length}"));
+                    var fileName = match.Result("${filename}");
+
+                    var fileInfo = fileSystemInfo as FileInfo;
+
+                    if (fileInfo == null)
+                        fileInfo = new FileInfo(string.Format("{0}{1}{2}", currentDirectoryFullName, Path.DirectorySeparatorChar, fileName));
+
+                    using (var output = fileInfo.OpenWrite())
+                    {
+                        this.InternalFileDownload(channel, input, output, fileName, length);
+                    }
+
+                    fileInfo.LastAccessTime = accessedTime;
+                    fileInfo.LastWriteTime = modifiedTime;
+
+                    if (fileSystemInfo is FileInfo)
+                        break;
+
+                    continue;
+                }
+
+                match = _timestampRe.Match(message);
+                if (match.Success)
+                {
+                    //  Read timestamp
+                    this.SendConfirmation(channel); //  Send reply
+
+                    var mtime = long.Parse(match.Result("${mtime}"));
+                    var atime = long.Parse(match.Result("${atime}"));
+
+                    var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+                    modifiedTime = zeroTime.AddSeconds(mtime);
+                    accessedTime = zeroTime.AddSeconds(atime);
+                    continue;
+                }
+
+                this.SendConfirmation(channel, 1, string.Format("\"{0}\" is not valid protocol message.", message));
+            }
+        }
+
+        partial void SendData(ChannelSession channel, string command)
+        {
+            this.Session.SendMessage(new ChannelDataMessage(channel.RemoteChannelNumber, System.Text.Encoding.Default.GetBytes(command)));
+        }
+    }
+}

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

@@ -0,0 +1,376 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Renci.SshNet.Channels;
+using System.IO;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Diagnostics;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// Provides SCP client functionality.
+    /// </summary>
+    public partial class ScpClient : BaseClient
+    {
+        private static Regex _fileInfoRe = new Regex(@"C(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)");
+
+        private static Regex _directoryInfoRe = new Regex(@"D(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)");
+
+        private static Regex _timestampRe = new Regex(@"T(?<mtime>\d+) 0 (?<atime>\d+) 0");
+
+        private static char[] _byteToChar;
+
+        /// <summary>
+        /// Gets or sets the operation timeout.
+        /// </summary>
+        /// <value>The operation timeout.</value>
+        public TimeSpan OperationTimeout { get; set; }
+
+        /// <summary>
+        /// Gets or sets the size of the buffer.
+        /// </summary>
+        /// <value>The size of the buffer.</value>
+        public uint BufferSize { get; set; }
+
+        /// <summary>
+        /// Occurs when downloading file.
+        /// </summary>
+        public event EventHandler<ScpDownloadEventArgs> Downloading;
+
+        /// <summary>
+        /// Occurs when uploading file.
+        /// </summary>
+        public event EventHandler<ScpUploadEventArgs> Uploading;
+
+        #region Constructors
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SftpClient"/> class.
+        /// </summary>
+        /// <param name="connectionInfo">The connection info.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is null.</exception>
+        public ScpClient(ConnectionInfo connectionInfo)
+            : base(connectionInfo)
+        {
+            this.OperationTimeout = new TimeSpan(0, 0, 0, 0, -1);
+            this.BufferSize = 1024 * 32 - 38;
+
+            if (_byteToChar == null)
+            {
+                _byteToChar = new char[128];
+                var ch = '\0';
+                for (int i = 0; i < 128; i++)
+                {
+                    _byteToChar[i] = ch++;
+                }
+            }
+
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SftpClient"/> class.
+        /// </summary>
+        /// <param name="host">Connection host.</param>
+        /// <param name="port">Connection port.</param>
+        /// <param name="username">Authentication username.</param>
+        /// <param name="password">Authentication password.</param>
+        /// <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>
+        public ScpClient(string host, int port, string username, string password)
+            : this(new PasswordConnectionInfo(host, port, username, password))
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SftpClient"/> class.
+        /// </summary>
+        /// <param name="host">Connection host.</param>
+        /// <param name="username">Authentication username.</param>
+        /// <param name="password">Authentication password.</param>
+        /// <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>
+        public ScpClient(string host, string username, string password)
+            : this(host, 22, username, password)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SftpClient"/> class.
+        /// </summary>
+        /// <param name="host">Connection host.</param>
+        /// <param name="port">Connection port.</param>
+        /// <param name="username">Authentication username.</param>
+        /// <param name="keyFiles">Authentication private key file(s) .</param>
+        /// <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>
+        public ScpClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
+            : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles))
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SftpClient"/> class.
+        /// </summary>
+        /// <param name="host">Connection host.</param>
+        /// <param name="username">Authentication username.</param>
+        /// <param name="keyFiles">Authentication private key file(s) .</param>
+        /// <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>
+        public ScpClient(string host, string username, params PrivateKeyFile[] keyFiles)
+            : this(host, 22, username, keyFiles)
+        {
+        }
+
+        #endregion
+
+        /// <summary>
+        /// Uploads the specified stream to the remote host.
+        /// </summary>
+        /// <param name="source">Stream to upload.</param>
+        /// <param name="filename">Remote host file name.</param>
+        public void Upload(Stream source, string filename)
+        {
+            using (var input = new PipeStream())
+            using (var channel = this.Session.CreateChannel<ChannelSession>())
+            {
+                channel.DataReceived += delegate(object sender, Common.ChannelDataEventArgs e)
+                {
+                    input.Write(e.Data, 0, e.Data.Length);
+                    input.Flush();
+                };
+
+                channel.Open();
+
+                //  Send channel command request
+                channel.SendExecRequest(string.Format("scp -qt \"{0}\"", filename));
+                this.CheckReturnCode(input);
+
+                this.InternalFileUpload(channel, input, source, filename);
+
+                channel.Close();
+            }
+        }
+
+        /// <summary>
+        /// Downloads the specified file from the remote host to the stream.
+        /// </summary>
+        /// <param name="filename">Remote host file name.</param>
+        /// <param name="destination">The stream where to download remote file.</param>
+        public void Download(string filename, Stream destination)
+        {
+            using (var input = new PipeStream())
+            using (var channel = this.Session.CreateChannel<ChannelSession>())
+            {
+                channel.DataReceived += delegate(object sender, Common.ChannelDataEventArgs e)
+                {
+                    input.Write(e.Data, 0, e.Data.Length);
+                    input.Flush();
+                };
+
+                channel.Open();
+
+                //  Send channel command request
+                channel.SendExecRequest(string.Format("scp -qf \"{0}\"", filename));
+                this.SendConfirmation(channel); //  Send reply
+
+                var message = ReadString(input);
+                var match = _fileInfoRe.Match(message);
+
+                if (match.Success)
+                {
+                    //  Read file
+                    this.SendConfirmation(channel); //  Send reply
+
+                    var mode = match.Result("${mode}");
+                    var length = long.Parse(match.Result("${length}"));
+                    var fileName = match.Result("${filename}");
+
+                    this.InternalFileDownload(channel, input, destination, fileName, length);
+                }
+                else
+                {
+                    this.SendConfirmation(channel, 1, string.Format("\"{0}\" is not valid protocol message.", message));
+                }
+
+                channel.Close();
+            }
+        }
+
+        private void InternalSetTimestamp(ChannelSession channel, Stream input, DateTime lastWriteTime, DateTime lastAccessime)
+        {
+            var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+            var modificationSeconds = (long)(lastWriteTime - zeroTime).TotalSeconds;
+            var accessSeconds = (long)(lastAccessime - zeroTime).TotalSeconds;
+            this.SendData(channel, string.Format("T{0} 0 {1} 0\n", modificationSeconds, accessSeconds));
+            this.CheckReturnCode(input);
+        }
+
+        private void InternalFileUpload(ChannelSession channel, Stream input, Stream source, string filename)
+        {
+            var length = source.Length;
+
+            this.SendData(channel, string.Format("C0644 {0} {1}\n", length, filename));
+
+            var buffer = new byte[this.BufferSize];
+
+            var read = source.Read(buffer, 0, buffer.Length);
+
+            long totalRead = 0;
+
+            while (read > 0)
+            {
+                this.SendData(channel, buffer, read);
+
+                totalRead += read;
+
+                this.RaiseUploadingEvent(filename, length, totalRead);
+
+                read = source.Read(buffer, 0, buffer.Length);
+            }
+
+            this.SendConfirmation(channel);
+            this.CheckReturnCode(input);
+        }
+
+        private void InternalFileDownload(ChannelSession channel, Stream input, Stream output, string filename, long length)
+        {
+            var buffer = new byte[Math.Min(length, this.BufferSize)];
+            var needToRead = length;
+
+            do
+            {
+                var read = input.Read(buffer, 0, (int)Math.Min(needToRead, this.BufferSize));
+
+                output.Write(buffer, 0, read);
+
+                this.RaiseDownloadingEvent(filename, length, length - needToRead);
+
+                needToRead -= read;
+            }
+            while (needToRead > 0);
+
+            output.Flush();
+
+            //  Raise one more time when file downloaded
+            this.RaiseDownloadingEvent(filename, length, length - needToRead);
+
+            //  Send confirmation byte after last data byte was read
+            this.SendConfirmation(channel);
+
+            this.CheckReturnCode(input);
+        }
+
+        private void RaiseDownloadingEvent(string filename, long size, long downloaded)
+        {
+            if (this.Downloading != null)
+            {
+                this.Downloading(this, new ScpDownloadEventArgs(filename, size, downloaded));
+            }
+        }
+
+        private void RaiseUploadingEvent(string filename, long size, long uploaded)
+        {
+            if (this.Uploading != null)
+            {
+                this.Uploading(this, new ScpUploadEventArgs(filename, size, uploaded));
+            }
+        }
+
+        private void SendConfirmation(ChannelSession channel)
+        {
+            this.SendData(channel, new byte[] { 0 });
+        }
+
+        private void SendConfirmation(ChannelSession channel, byte errorCode, string message)
+        {
+            this.SendData(channel, new byte[] { errorCode });
+            this.SendData(channel, string.Format("{0}\n", message));
+        }
+
+        /// <summary>
+        /// Checks the return code.
+        /// </summary>
+        /// <param name="input">The output stream.</param>
+        private void CheckReturnCode(Stream input)
+        {
+            var b = ReadByte(input);
+
+            if (b > 0)
+            {
+                var errorText = ReadString(input);
+
+                throw new ScpException(errorText);
+            }
+        }
+
+        partial void SendData(ChannelSession channel, string command);
+
+        private void SendData(ChannelSession channel, byte[] buffer, int length)
+        {
+            if (length == buffer.Length)
+            {
+                this.Session.SendMessage(new ChannelDataMessage(channel.RemoteChannelNumber, buffer));
+            }
+            else
+            {
+                this.Session.SendMessage(new ChannelDataMessage(channel.RemoteChannelNumber, buffer.Take(length).ToArray()));
+            }
+        }
+
+        private void SendData(ChannelSession channel, byte[] buffer)
+        {
+            this.Session.SendMessage(new ChannelDataMessage(channel.RemoteChannelNumber, buffer));
+        }
+
+        private static int ReadByte(Stream stream)
+        {
+            var b = stream.ReadByte();
+
+            while (b < 0)
+            {
+                Thread.Sleep(100);
+                b = stream.ReadByte();
+            }
+
+            return b;
+        }
+
+        private static string ReadString(Stream stream)
+        {
+            var hasError = false;
+
+            StringBuilder sb = new StringBuilder();
+
+            var b = ReadByte(stream);
+
+            if (b == 1 || b == 2)
+            {
+                hasError = true;
+                b = ReadByte(stream);
+            }
+
+            var ch = _byteToChar[b];
+
+            while (ch != '\n')
+            {
+                sb.Append(ch);
+
+                b = ReadByte(stream);
+
+                ch = _byteToChar[b];
+            }
+
+            if (hasError)
+                throw new ScpException(sb.ToString());
+
+            return sb.ToString();
+        }
+    }
+}

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

@@ -122,7 +122,7 @@ namespace Renci.SshNet
 
         partial void Log(string text)
         {
-            this._log.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 1, text);
+            //this._log.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 1, text);
         }
     }
 }

+ 2 - 2
Renci.SshClient/Renci.SshNet/SshClient.cs

@@ -225,10 +225,10 @@ namespace Renci.SshNet
         /// <returns>Returns an instance of <see cref="SshCommand"/> with execution results.</returns>
         /// <remarks>This method internally uses asynchronous calls.</remarks>
         /// <exception cref="ArgumentException">CommandText property is empty.</exception>
-        /// <exception cref="SshException">Invalid Operation - An existing channel was used to execute this command.</exception>
+        /// <exception cref="Renci.SshNet.Common.SshException">Invalid Operation - An existing channel was used to execute this command.</exception>
         /// <exception cref="InvalidOperationException">Asynchronous operation is already in progress.</exception>
         public SshCommand RunCommand(string commandText)
-        {
+        {            
             var cmd = this.CreateCommand(commandText);
             cmd.Execute();
             return cmd;