Selaa lähdekoodia

Renamed Quote transformation and corresponding class to ShellQuote.
Added DoubleQuote transformation.
Modified default remote path transformation for ScpClient to DoubleQuote.

Gert Driesen 8 vuotta sitten
vanhempi
sitoutus
e434f97be5
21 muutettua tiedostoa jossa 689 lisäystä ja 317 poistoa
  1. 4 4
      src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj
  2. 4 1
      src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj
  3. 228 78
      src/Renci.SshNet.Tests/Classes/ScpClientTest.cs
  4. 45 0
      src/Renci.SshNet.Tests/Classes/ScpClientTestBase.cs
  5. 26 26
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs
  6. 25 26
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs
  7. 26 26
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs
  8. 26 26
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs
  9. 27 27
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs
  10. 20 34
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs
  11. 26 26
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs
  12. 1 0
      src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
  13. 5 4
      src/Renci.SshNet/IServiceFactory.cs
  14. 11 0
      src/Renci.SshNet/PasswordAuthenticationMethod.cs
  15. 76 0
      src/Renci.SshNet/RemotePathDoubleQuoteTransformation.cs
  16. 4 0
      src/Renci.SshNet/RemotePathNoneTransformation.cs
  17. 46 10
      src/Renci.SshNet/RemotePathShellQuoteTransformation.cs
  18. 72 20
      src/Renci.SshNet/RemotePathTransformation.cs
  19. 2 1
      src/Renci.SshNet/Renci.SshNet.csproj
  20. 9 3
      src/Renci.SshNet/ScpClient.cs
  21. 6 5
      src/Renci.SshNet/ServiceFactory.cs

+ 4 - 4
src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj

@@ -581,14 +581,14 @@
     <Compile Include="..\Renci.SshNet\ProxyTypes.cs">
       <Link>ProxyTypes.cs</Link>
     </Compile>
-    <Compile Include="..\Renci.SshNet\RemotePathEscapeTransformation.cs">
-      <Link>RemotePathEscapeTransformation.cs</Link>
+    <Compile Include="..\Renci.SshNet\RemotePathDoubleQuoteTransformation.cs">
+      <Link>RemotePathDoubleQuoteTransformation.cs</Link>
     </Compile>
     <Compile Include="..\Renci.SshNet\RemotePathNoneTransformation.cs">
       <Link>RemotePathNoneTransformation.cs</Link>
     </Compile>
-    <Compile Include="..\Renci.SshNet\RemotePathQuoteTransformation.cs">
-      <Link>RemotePathQuoteTransformation.cs</Link>
+    <Compile Include="..\Renci.SshNet\RemotePathShellQuoteTransformation.cs">
+      <Link>RemotePathShellQuoteTransformation.cs</Link>
     </Compile>
     <Compile Include="..\Renci.SshNet\RemotePathTransformation.cs">
       <Link>RemotePathTransformation.cs</Link>

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

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

+ 228 - 78
src/Renci.SshNet.Tests/Classes/ScpClientTest.cs

@@ -15,6 +15,206 @@ namespace Renci.SshNet.Tests.Classes
     [TestClass]
     public partial class ScpClientTest : TestBase
     {
+        private Random _random;
+
+        [TestInitialize]
+        public void SetUp()
+        {
+            _random = new Random();
+        }
+
+        [TestMethod]
+        public void Ctor_ConnectionInfo_Null()
+        {
+            const ConnectionInfo connectionInfo = null;
+
+            try
+            {
+                new ScpClient(connectionInfo);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("connectionInfo", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void Ctor_ConnectionInfo_NotNull()
+        {
+            var connectionInfo = new ConnectionInfo("HOST", "USER", new PasswordAuthenticationMethod("USER", "PWD"));
+
+            var client = new ScpClient(connectionInfo);
+            Assert.AreEqual(16 * 1024U, client.BufferSize);
+            Assert.AreSame(connectionInfo, client.ConnectionInfo);
+            Assert.IsFalse(client.IsConnected);
+            Assert.AreEqual(new TimeSpan(0, 0, 0, 0, -1), client.KeepAliveInterval);
+            Assert.AreEqual(new TimeSpan(0, 0, 0, 0, -1), client.OperationTimeout);
+            Assert.AreSame(RemotePathTransformation.DoubleQuote, client.RemotePathTransformation);
+            Assert.IsNull(client.Session);
+        }
+
+        [TestMethod]
+        public void Ctor_HostAndPortAndUsernameAndPassword()
+        {
+            var host = _random.Next().ToString();
+            var port = _random.Next(1, 100);
+            var userName = _random.Next().ToString();
+            var password = _random.Next().ToString();
+
+            var client = new ScpClient(host, port, userName, password);
+            Assert.AreEqual(16 * 1024U, client.BufferSize);
+            Assert.IsNotNull(client.ConnectionInfo);
+            Assert.IsFalse(client.IsConnected);
+            Assert.AreEqual(new TimeSpan(0, 0, 0, 0, -1), client.KeepAliveInterval);
+            Assert.AreEqual(new TimeSpan(0, 0, 0, 0, -1), client.OperationTimeout);
+            Assert.AreSame(RemotePathTransformation.DoubleQuote, client.RemotePathTransformation);
+            Assert.IsNull(client.Session);
+
+            var passwordConnectionInfo = client.ConnectionInfo as PasswordConnectionInfo;
+            Assert.IsNotNull(passwordConnectionInfo);
+            Assert.AreEqual(host, passwordConnectionInfo.Host);
+            Assert.AreEqual(port, passwordConnectionInfo.Port);
+            Assert.AreSame(userName, passwordConnectionInfo.Username);
+            Assert.IsNotNull(passwordConnectionInfo.AuthenticationMethods);
+            Assert.AreEqual(1, passwordConnectionInfo.AuthenticationMethods.Count);
+
+            var passwordAuthentication = passwordConnectionInfo.AuthenticationMethods[0] as PasswordAuthenticationMethod;
+            Assert.IsNotNull(passwordAuthentication);
+            Assert.AreEqual(userName, passwordAuthentication.Username);
+            Assert.IsTrue(Encoding.UTF8.GetBytes(password).IsEqualTo(passwordAuthentication.Password));
+        }
+
+        [TestMethod]
+        public void Ctor_HostAndUsernameAndPassword()
+        {
+            var host = _random.Next().ToString();
+            var userName = _random.Next().ToString();
+            var password = _random.Next().ToString();
+
+            var client = new ScpClient(host, userName, password);
+            Assert.AreEqual(16 * 1024U, client.BufferSize);
+            Assert.IsNotNull(client.ConnectionInfo);
+            Assert.IsFalse(client.IsConnected);
+            Assert.AreEqual(new TimeSpan(0, 0, 0, 0, -1), client.KeepAliveInterval);
+            Assert.AreEqual(new TimeSpan(0, 0, 0, 0, -1), client.OperationTimeout);
+            Assert.AreSame(RemotePathTransformation.DoubleQuote, client.RemotePathTransformation);
+            Assert.IsNull(client.Session);
+
+            var passwordConnectionInfo = client.ConnectionInfo as PasswordConnectionInfo;
+            Assert.IsNotNull(passwordConnectionInfo);
+            Assert.AreEqual(host, passwordConnectionInfo.Host);
+            Assert.AreEqual(22, passwordConnectionInfo.Port);
+            Assert.AreSame(userName, passwordConnectionInfo.Username);
+            Assert.IsNotNull(passwordConnectionInfo.AuthenticationMethods);
+            Assert.AreEqual(1, passwordConnectionInfo.AuthenticationMethods.Count);
+
+            var passwordAuthentication = passwordConnectionInfo.AuthenticationMethods[0] as PasswordAuthenticationMethod;
+            Assert.IsNotNull(passwordAuthentication);
+            Assert.AreEqual(userName, passwordAuthentication.Username);
+            Assert.IsTrue(Encoding.UTF8.GetBytes(password).IsEqualTo(passwordAuthentication.Password));
+        }
+
+        [TestMethod]
+        public void Ctor_HostAndPortAndUsernameAndPrivateKeys()
+        {
+            var host = _random.Next().ToString();
+            var port = _random.Next(1, 100);
+            var userName = _random.Next().ToString();
+            var privateKeys = new[] {GetRsaKey(), GetDsaKey()};
+
+            var client = new ScpClient(host, port, userName, privateKeys);
+            Assert.AreEqual(16 * 1024U, client.BufferSize);
+            Assert.IsNotNull(client.ConnectionInfo);
+            Assert.IsFalse(client.IsConnected);
+            Assert.AreEqual(new TimeSpan(0, 0, 0, 0, -1), client.KeepAliveInterval);
+            Assert.AreEqual(new TimeSpan(0, 0, 0, 0, -1), client.OperationTimeout);
+            Assert.AreSame(RemotePathTransformation.DoubleQuote, client.RemotePathTransformation);
+            Assert.IsNull(client.Session);
+
+            var privateKeyConnectionInfo = client.ConnectionInfo as PrivateKeyConnectionInfo;
+            Assert.IsNotNull(privateKeyConnectionInfo);
+            Assert.AreEqual(host, privateKeyConnectionInfo.Host);
+            Assert.AreEqual(port, privateKeyConnectionInfo.Port);
+            Assert.AreSame(userName, privateKeyConnectionInfo.Username);
+            Assert.IsNotNull(privateKeyConnectionInfo.AuthenticationMethods);
+            Assert.AreEqual(1, privateKeyConnectionInfo.AuthenticationMethods.Count);
+
+            var privateKeyAuthentication = privateKeyConnectionInfo.AuthenticationMethods[0] as PrivateKeyAuthenticationMethod;
+            Assert.IsNotNull(privateKeyAuthentication);
+            Assert.AreEqual(userName, privateKeyAuthentication.Username);
+            Assert.IsNotNull(privateKeyAuthentication.KeyFiles);
+            Assert.AreEqual(privateKeys.Length, privateKeyAuthentication.KeyFiles.Count);
+            Assert.IsTrue(privateKeyAuthentication.KeyFiles.Contains(privateKeys[0]));
+            Assert.IsTrue(privateKeyAuthentication.KeyFiles.Contains(privateKeys[1]));
+        }
+
+        [TestMethod]
+        public void Ctor_HostAndUsernameAndPrivateKeys()
+        {
+            var host = _random.Next().ToString();
+            var userName = _random.Next().ToString();
+            var privateKeys = new[] { GetRsaKey(), GetDsaKey() };
+
+            var client = new ScpClient(host, userName, privateKeys);
+            Assert.AreEqual(16 * 1024U, client.BufferSize);
+            Assert.IsNotNull(client.ConnectionInfo);
+            Assert.IsFalse(client.IsConnected);
+            Assert.AreEqual(new TimeSpan(0, 0, 0, 0, -1), client.KeepAliveInterval);
+            Assert.AreEqual(new TimeSpan(0, 0, 0, 0, -1), client.OperationTimeout);
+            Assert.AreSame(RemotePathTransformation.DoubleQuote, client.RemotePathTransformation);
+            Assert.IsNull(client.Session);
+
+            var privateKeyConnectionInfo = client.ConnectionInfo as PrivateKeyConnectionInfo;
+            Assert.IsNotNull(privateKeyConnectionInfo);
+            Assert.AreEqual(host, privateKeyConnectionInfo.Host);
+            Assert.AreEqual(22, privateKeyConnectionInfo.Port);
+            Assert.AreSame(userName, privateKeyConnectionInfo.Username);
+            Assert.IsNotNull(privateKeyConnectionInfo.AuthenticationMethods);
+            Assert.AreEqual(1, privateKeyConnectionInfo.AuthenticationMethods.Count);
+
+            var privateKeyAuthentication = privateKeyConnectionInfo.AuthenticationMethods[0] as PrivateKeyAuthenticationMethod;
+            Assert.IsNotNull(privateKeyAuthentication);
+            Assert.AreEqual(userName, privateKeyAuthentication.Username);
+            Assert.IsNotNull(privateKeyAuthentication.KeyFiles);
+            Assert.AreEqual(privateKeys.Length, privateKeyAuthentication.KeyFiles.Count);
+            Assert.IsTrue(privateKeyAuthentication.KeyFiles.Contains(privateKeys[0]));
+            Assert.IsTrue(privateKeyAuthentication.KeyFiles.Contains(privateKeys[1]));
+        }
+
+        [TestMethod]
+        public void RemotePathTransformation_Value_NotNull()
+        {
+            var client = new ScpClient("HOST", 22, "USER", "PWD");
+
+            Assert.AreSame(RemotePathTransformation.DoubleQuote, client.RemotePathTransformation);
+            client.RemotePathTransformation = RemotePathTransformation.ShellQuote;
+            Assert.AreSame(RemotePathTransformation.ShellQuote, client.RemotePathTransformation);
+        }
+
+        [TestMethod]
+        public void RemotePathTransformation_Value_Null()
+        {
+            var client = new ScpClient("HOST", 22, "USER", "PWD")
+            {
+                RemotePathTransformation = RemotePathTransformation.ShellQuote
+            };
+
+            try
+            {
+                client.RemotePathTransformation = null;
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("value", ex.ParamName);
+            }
+
+            Assert.AreSame(RemotePathTransformation.ShellQuote, client.RemotePathTransformation);
+        }
+
         [TestMethod]
         [TestCategory("Scp")]
         [TestCategory("integration")]
@@ -172,10 +372,12 @@ namespace Renci.SshNet.Tests.Classes
             {
                 scp.Connect();
 
-                var uploadDirectory = Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName()));
+                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));
+                    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);
@@ -185,7 +387,8 @@ namespace Renci.SshNet.Tests.Classes
 
                 scp.Upload(uploadDirectory, "uploaded_dir");
 
-                var downloadDirectory = Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName()));
+                var downloadDirectory =
+                    Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName()));
 
                 scp.Download("uploaded_dir", downloadDirectory);
 
@@ -193,11 +396,12 @@ namespace Renci.SshNet.Tests.Classes
                 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;
+                    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();
 
@@ -331,76 +535,6 @@ namespace Renci.SshNet.Tests.Classes
             Assert.Inconclusive("A method that does not return a value cannot be verified.");
         }
 
-        /// <summary>
-        ///A test for ScpClient Constructor
-        ///</summary>
-        [TestMethod]
-        [Ignore] // placeholder for actual test
-        public void ScpClientConstructorTest()
-        {
-            string host = string.Empty; // TODO: Initialize to an appropriate value
-            string username = string.Empty; // TODO: Initialize to an appropriate value
-            PrivateKeyFile[] keyFiles = null; // TODO: Initialize to an appropriate value
-            ScpClient target = new ScpClient(host, username, keyFiles);
-            Assert.Inconclusive("TODO: Implement code to verify target");
-        }
-
-        /// <summary>
-        ///A test for ScpClient Constructor
-        ///</summary>
-        [TestMethod]
-        [Ignore] // placeholder for actual test
-        public void ScpClientConstructorTest1()
-        {
-            string host = string.Empty; // TODO: Initialize to an appropriate value
-            int port = 0; // TODO: Initialize to an appropriate value
-            string username = string.Empty; // TODO: Initialize to an appropriate value
-            PrivateKeyFile[] keyFiles = null; // TODO: Initialize to an appropriate value
-            ScpClient target = new ScpClient(host, port, username, keyFiles);
-            Assert.Inconclusive("TODO: Implement code to verify target");
-        }
-
-        /// <summary>
-        ///A test for ScpClient Constructor
-        ///</summary>
-        [TestMethod]
-        [Ignore] // placeholder for actual test
-        public void ScpClientConstructorTest2()
-        {
-            string host = string.Empty; // TODO: Initialize to an appropriate value
-            string username = string.Empty; // TODO: Initialize to an appropriate value
-            string password = string.Empty; // TODO: Initialize to an appropriate value
-            ScpClient target = new ScpClient(host, username, password);
-            Assert.Inconclusive("TODO: Implement code to verify target");
-        }
-
-        /// <summary>
-        ///A test for ScpClient Constructor
-        ///</summary>
-        [TestMethod]
-        [Ignore] // placeholder for actual test
-        public void ScpClientConstructorTest3()
-        {
-            string host = string.Empty; // TODO: Initialize to an appropriate value
-            int port = 0; // TODO: Initialize to an appropriate value
-            string username = string.Empty; // TODO: Initialize to an appropriate value
-            string password = string.Empty; // TODO: Initialize to an appropriate value
-            ScpClient target = new ScpClient(host, port, username, password);
-            Assert.Inconclusive("TODO: Implement code to verify target");
-        }
-
-        /// <summary>
-        ///A test for ScpClient Constructor
-        ///</summary>
-        [TestMethod]
-        [Ignore] // placeholder for actual test
-        public void ScpClientConstructorTest4()
-        {
-            ConnectionInfo connectionInfo = null; // TODO: Initialize to an appropriate value
-            ScpClient target = new ScpClient(connectionInfo);
-            Assert.Inconclusive("TODO: Implement code to verify target");
-        }
-
         protected static string CalculateMD5(string fileName)
         {
             using (var file = new FileStream(fileName, FileMode.Open))
@@ -427,5 +561,21 @@ namespace Renci.SshNet.Tests.Classes
                 client.Disconnect();
             }
         }
+
+        private PrivateKeyFile GetRsaKey()
+        {
+            using (var stream = GetData("Key.RSA.txt"))
+            {
+                return new PrivateKeyFile(stream);
+            }
+        }
+
+        private PrivateKeyFile GetDsaKey()
+        {
+            using (var stream = GetData("Key.SSH2.DSA.txt"))
+            {
+                return new PrivateKeyFile(stream);
+            }
+        }
     }
 }

+ 45 - 0
src/Renci.SshNet.Tests/Classes/ScpClientTestBase.cs

@@ -0,0 +1,45 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    public abstract class ScpClientTestBase
+    {
+        internal Mock<IServiceFactory> _serviceFactoryMock;
+        internal Mock<IRemotePathTransformation> _remotePathTransformationMock;
+        internal Mock<ISession> _sessionMock;
+        internal Mock<IChannelSession> _channelSessionMock;
+        internal Mock<PipeStream> _pipeStreamMock;
+
+        protected abstract void SetupData();
+
+        protected void CreateMocks()
+        {
+            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
+            _remotePathTransformationMock = new Mock<IRemotePathTransformation>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+            _pipeStreamMock = new Mock<PipeStream>(MockBehavior.Strict);
+        }
+
+        protected abstract void SetupMocks();
+
+        protected virtual void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+        }
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        protected abstract void Act();
+    }
+}

+ 26 - 26
src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs

@@ -4,66 +4,66 @@ using System.Globalization;
 using System.IO;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
-using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse
+    public class ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse : ScpClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
-        private Mock<IChannelSession> _channelSessionMock;
-        private Mock<PipeStream> _pipeStreamMock;
         private ConnectionInfo _connectionInfo;
         private ScpClient _scpClient;
         private DirectoryInfo _directoryInfo;
         private string _path;
-        private string _quotedPath;
+        private string _transformedPath;
         private IList<ScpUploadEventArgs> _uploadingRegister;
         private SshException _actualException;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
-        protected void Arrange()
+        protected override void SetupData()
         {
             var random = new Random();
+
             _connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
             _directoryInfo = new DirectoryInfo("destination");
             _path = "/home/sshnet/" + random.Next().ToString(CultureInfo.InvariantCulture);
-            _quotedPath = _path.ShellQuote();
+            _transformedPath = random.Next().ToString();
             _uploadingRegister = new List<ScpUploadEventArgs>();
+        }
 
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
-            _pipeStreamMock = new Mock<PipeStream>(MockBehavior.Strict);
-
+        protected override void SetupMocks()
+        {
             var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
+                               .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateSession(_connectionInfo))
-                .Returns(_sessionMock.Object);
+                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.CreateChannelSession()).Returns(_channelSessionMock.Object);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Open());
+            _remotePathTransformationMock.InSequence(sequence)
+                                         .Setup(p => p.Transform(_path))
+                                         .Returns(_transformedPath);
             _channelSessionMock.InSequence(sequence)
-                .Setup(p => p.SendExecRequest(string.Format("scp -prf {0}", _quotedPath))).Returns(false);
+                               .Setup(p => p.SendExecRequest(string.Format("scp -prf {0}", _transformedPath)))
+                               .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
 
             _scpClient = new ScpClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
             _scpClient.Connect();
         }
 
-        protected virtual void Act()
+        protected override void Act()
         {
             try
             {
@@ -87,7 +87,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void SendExecRequestOnChannelSessionShouldBeInvokedOnce()
         {
-            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -prf {0}", _quotedPath)), Times.Once);
+            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -prf {0}", _transformedPath)), Times.Once);
         }
 
         [TestMethod]

+ 25 - 26
src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs

@@ -4,66 +4,65 @@ using System.Globalization;
 using System.IO;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
-using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse
+    public class ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse : ScpClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
-        private Mock<IChannelSession> _channelSessionMock;
-        private Mock<PipeStream> _pipeStreamMock;
         private ConnectionInfo _connectionInfo;
         private ScpClient _scpClient;
         private FileInfo _fileInfo;
         private string _path;
-        private string _quotedPath;
+        private string _transformedPath;
         private IList<ScpUploadEventArgs> _uploadingRegister;
         private SshException _actualException;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
-        protected void Arrange()
+        protected override void SetupData()
         {
             var random = new Random();
+
             _connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
             _fileInfo = new FileInfo("destination");
             _path = "/home/sshnet/" + random.Next().ToString(CultureInfo.InvariantCulture);
-            _quotedPath = _path.ShellQuote();
+            _transformedPath = random.Next().ToString();
             _uploadingRegister = new List<ScpUploadEventArgs>();
+        }
 
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
-            _pipeStreamMock = new Mock<PipeStream>(MockBehavior.Strict);
-
+        protected override void SetupMocks()
+        {
             var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
+                               .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateSession(_connectionInfo))
-                .Returns(_sessionMock.Object);
+                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.CreateChannelSession()).Returns(_channelSessionMock.Object);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Open());
+            _remotePathTransformationMock.InSequence(sequence)
+                                         .Setup(p => p.Transform(_path))
+                                         .Returns(_transformedPath);
             _channelSessionMock.InSequence(sequence)
-                .Setup(p => p.SendExecRequest(string.Format("scp -pf {0}", _quotedPath))).Returns(false);
+                .Setup(p => p.SendExecRequest(string.Format("scp -pf {0}", _transformedPath))).Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
 
             _scpClient = new ScpClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
             _scpClient.Connect();
         }
 
-        protected virtual void Act()
+        protected override void Act()
         {
             try
             {
@@ -87,7 +86,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void SendExecRequestOnChannelSessionShouldBeInvokedOnce()
         {
-            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -pf {0}", _quotedPath)), Times.Once);
+            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -pf {0}", _transformedPath)), Times.Once);
         }
 
         [TestMethod]

+ 26 - 26
src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs

@@ -4,33 +4,21 @@ using System.Globalization;
 using System.IO;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
-using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse
+    public class ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse : ScpClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
-        private Mock<IChannelSession> _channelSessionMock;
-        private Mock<PipeStream> _pipeStreamMock;
         private ConnectionInfo _connectionInfo;
         private ScpClient _scpClient;
         private Stream _destination;
         private string _path;
-        private string _quotedPath;
+        private string _transformedPath;
         private IList<ScpUploadEventArgs> _uploadingRegister;
         private SshException _actualException;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
         [TestCleanup]
         public void Cleanup()
         {
@@ -40,39 +28,51 @@ namespace Renci.SshNet.Tests.Classes
             }
         }
 
-        protected void Arrange()
+        protected override void SetupData()
         {
             var random = new Random();
+
             _connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
             _destination = new MemoryStream();
             _path = "/home/sshnet/" + random.Next().ToString(CultureInfo.InvariantCulture);
-            _quotedPath = _path.ShellQuote();
+            _transformedPath = random.Next().ToString();
             _uploadingRegister = new List<ScpUploadEventArgs>();
+        }
 
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
-            _pipeStreamMock = new Mock<PipeStream>(MockBehavior.Strict);
-
+        protected override void SetupMocks()
+        {
             var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
+                               .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateSession(_connectionInfo))
-                .Returns(_sessionMock.Object);
+                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.CreateChannelSession()).Returns(_channelSessionMock.Object);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Open());
+            _remotePathTransformationMock.InSequence(sequence)
+                                         .Setup(p => p.Transform(_path))
+                                         .Returns(_transformedPath);
             _channelSessionMock.InSequence(sequence)
-                .Setup(p => p.SendExecRequest(string.Format("scp -f {0}", _quotedPath))).Returns(false);
+                               .Setup(p => p.SendExecRequest(string.Format("scp -f {0}", _transformedPath)))
+                               .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
 
             _scpClient = new ScpClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
             _scpClient.Connect();
         }
 
-        protected virtual void Act()
+        protected override void Act()
         {
             try
             {
@@ -96,7 +96,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void SendExecRequestOnChannelSessionShouldBeInvokedOnce()
         {
-            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -f {0}", _quotedPath)), Times.Once);
+            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -f {0}", _transformedPath)), Times.Once);
         }
 
         [TestMethod]

+ 26 - 26
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs

@@ -4,66 +4,66 @@ using System.Globalization;
 using System.IO;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
-using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse
+    public class ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse : ScpClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
-        private Mock<IChannelSession> _channelSessionMock;
-        private Mock<PipeStream> _pipeStreamMock;
         private ConnectionInfo _connectionInfo;
         private ScpClient _scpClient;
         private DirectoryInfo _directoryInfo;
         private string _path;
-        private string _quotedPath;
+        private string _transformedPath;
         private IList<ScpUploadEventArgs> _uploadingRegister;
         private SshException _actualException;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
-        protected void Arrange()
+        protected override void SetupData()
         {
             var random = new Random();
+
             _connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
             _directoryInfo = new DirectoryInfo("source");
             _path = "/home/sshnet/" + random.Next().ToString(CultureInfo.InvariantCulture);
-            _quotedPath = _path.ShellQuote();
+            _transformedPath = random.Next().ToString();
             _uploadingRegister = new List<ScpUploadEventArgs>();
+        }
 
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
-            _pipeStreamMock = new Mock<PipeStream>(MockBehavior.Strict);
-
+        protected override void SetupMocks()
+        {
             var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
+                               .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateSession(_connectionInfo))
-                .Returns(_sessionMock.Object);
+                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.CreateChannelSession()).Returns(_channelSessionMock.Object);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Open());
+            _remotePathTransformationMock.InSequence(sequence)
+                                         .Setup(p => p.Transform(_path))
+                                         .Returns(_transformedPath);
             _channelSessionMock.InSequence(sequence)
-                .Setup(p => p.SendExecRequest(string.Format("scp -rt {0}", _quotedPath))).Returns(false);
+                               .Setup(p => p.SendExecRequest(string.Format("scp -rt {0}", _transformedPath)))
+                               .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
 
             _scpClient = new ScpClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
             _scpClient.Connect();
         }
 
-        protected virtual void Act()
+        protected override void Act()
         {
             try
             {
@@ -87,7 +87,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void SendExecREquestOnChannelSessionShouldBeInvokedOnce()
         {
-            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -rt {0}", _quotedPath)), Times.Once);
+            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -rt {0}", _transformedPath)), Times.Once);
         }
 
         [TestMethod]

+ 27 - 27
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs

@@ -4,34 +4,22 @@ using System.Globalization;
 using System.IO;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
-using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse
+    public class ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse : ScpClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
-        private Mock<IChannelSession> _channelSessionMock;
-        private Mock<PipeStream> _pipeStreamMock;
         private ConnectionInfo _connectionInfo;
         private ScpClient _scpClient;
         private FileInfo _fileInfo;
         private string _path;
-        private string _quotedPath;
+        private string _transformedPath;
         private string _fileName;
         private IList<ScpUploadEventArgs> _uploadingRegister;
         private SshException _actualException;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
         [TestCleanup]
         public void Cleanup()
         {
@@ -42,40 +30,52 @@ namespace Renci.SshNet.Tests.Classes
             }
         }
 
-        protected void Arrange()
+        protected override void SetupData()
         {
             var random = new Random();
-            _fileName = CreateTemporaryFile(new byte[] {1});
+
+            _fileName = CreateTemporaryFile(new byte[] { 1 });
             _connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
             _fileInfo = new FileInfo(_fileName);
             _path = "/home/sshnet/" + random.Next().ToString(CultureInfo.InvariantCulture);
-            _quotedPath = _path.ShellQuote();
+            _transformedPath = _path.ShellQuote();
             _uploadingRegister = new List<ScpUploadEventArgs>();
+        }
 
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
-            _pipeStreamMock = new Mock<PipeStream>(MockBehavior.Strict);
-
+        protected override void SetupMocks()
+        {
             var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
+                               .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateSession(_connectionInfo))
-                .Returns(_sessionMock.Object);
+                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.CreateChannelSession()).Returns(_channelSessionMock.Object);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Open());
+            _remotePathTransformationMock.InSequence(sequence)
+                                         .Setup(p => p.Transform(_path))
+                                         .Returns(_transformedPath);
             _channelSessionMock.InSequence(sequence)
-                               .Setup(p => p.SendExecRequest(string.Format("scp -t {0}", _quotedPath))).Returns(false);
+                               .Setup(p => p.SendExecRequest(string.Format("scp -t {0}", _transformedPath)))
+                               .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
 
             _scpClient = new ScpClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
             _scpClient.Connect();
         }
 
-        protected virtual void Act()
+        protected override void Act()
         {
             try
             {
@@ -99,7 +99,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void SendExecRequestOnChannelSessionShouldBeInvokedOnce()
         {
-            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t {0}", _quotedPath)), Times.Once);
+            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t {0}", _transformedPath)), Times.Once);
         }
 
         [TestMethod]

+ 20 - 34
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs

@@ -6,36 +6,24 @@ using System.Linq;
 using System.Text;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
-using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class ScpClientTest_Upload_FileInfoAndPath_Success
+    public class ScpClientTest_Upload_FileInfoAndPath_Success : ScpClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
-        private Mock<IChannelSession> _channelSessionMock;
-        private Mock<PipeStream> _pipeStreamMock;
         private ConnectionInfo _connectionInfo;
         private ScpClient _scpClient;
         private FileInfo _fileInfo;
         private string _path;
-        private string _quotedPath;
+        private string _transformedPath;
         private int _bufferSize;
         private byte[] _fileContent;
         private string _fileName;
         private int _fileSize;
         private IList<ScpUploadEventArgs> _uploadingRegister;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
         [TestCleanup]
         public void Cleanup()
         {
@@ -46,7 +34,7 @@ namespace Renci.SshNet.Tests.Classes
             }
         }
 
-        private void SetupData()
+        protected override void SetupData()
         {
             var random = new Random();
 
@@ -57,30 +45,30 @@ namespace Renci.SshNet.Tests.Classes
             _connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
             _fileInfo = new FileInfo(_fileName);
             _path = "/home/sshnet/" + random.Next().ToString(CultureInfo.InvariantCulture);
-            _quotedPath = _path.ShellQuote();
+            _transformedPath = random.Next().ToString();
             _uploadingRegister = new List<ScpUploadEventArgs>();
         }
 
-        private void CreateMocks()
-        {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
-            _pipeStreamMock = new Mock<PipeStream>(MockBehavior.Strict);
-        }
-
-        private void SetupMocks()
+        protected override void SetupMocks()
         {
             var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
+                               .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateSession(_connectionInfo))
-                .Returns(_sessionMock.Object);
+                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.CreateChannelSession()).Returns(_channelSessionMock.Object);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Open());
+            _remotePathTransformationMock.InSequence(sequence)
+                                         .Setup(p => p.Transform(_path))
+                                         .Returns(_transformedPath);
             _channelSessionMock.InSequence(sequence)
-                               .Setup(p => p.SendExecRequest(string.Format("scp -t {0}", _quotedPath))).Returns(true);
+                               .Setup(p => p.SendExecRequest(string.Format("scp -t {0}", _transformedPath)))
+                               .Returns(true);
             _pipeStreamMock.InSequence(sequence).Setup(p => p.ReadByte()).Returns(0);
             _channelSessionMock.InSequence(sequence).Setup(p => p.SendData(It.IsAny<byte[]>()));
             _pipeStreamMock.InSequence(sequence).Setup(p => p.ReadByte()).Returns(0);
@@ -102,11 +90,9 @@ namespace Renci.SshNet.Tests.Classes
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
         }
 
-        protected void Arrange()
+        protected override void Arrange()
         {
-            SetupData();
-            CreateMocks();
-            SetupMocks();
+            base.Arrange();
 
             _scpClient = new ScpClient(_connectionInfo, false, _serviceFactoryMock.Object)
                 {
@@ -116,7 +102,7 @@ namespace Renci.SshNet.Tests.Classes
             _scpClient.Connect();
         }
 
-        protected virtual void Act()
+        protected override void Act()
         {
             _scpClient.Upload(_fileInfo, _path);
         }
@@ -124,7 +110,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void SendExecRequestOnChannelSessionShouldBeInvokedOnce()
         {
-            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t {0}", _quotedPath)), Times.Once);
+            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t {0}", _transformedPath)), Times.Once);
         }
 
         [TestMethod]

+ 26 - 26
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs

@@ -4,33 +4,21 @@ using System.Globalization;
 using System.IO;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
-using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse
+    public class ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse : ScpClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
-        private Mock<IChannelSession> _channelSessionMock;
-        private Mock<PipeStream> _pipeStreamMock;
         private ConnectionInfo _connectionInfo;
         private ScpClient _scpClient;
         private Stream _source;
         private string _path;
-        private string _quotedPath;
+        private string _transformedPath;
         private IList<ScpUploadEventArgs> _uploadingRegister;
         private SshException _actualException;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
         [TestCleanup]
         public void Cleanup()
         {
@@ -40,39 +28,51 @@ namespace Renci.SshNet.Tests.Classes
             }
         }
 
-        protected void Arrange()
+        protected override void SetupData()
         {
             var random = new Random();
+
             _connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
             _source = new MemoryStream();
             _path = "/home/sshnet/" + random.Next().ToString(CultureInfo.InvariantCulture);
-            _quotedPath = _path.ShellQuote();
+            _transformedPath = random.Next().ToString();
             _uploadingRegister = new List<ScpUploadEventArgs>();
+        }
 
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
-            _pipeStreamMock = new Mock<PipeStream>(MockBehavior.Strict);
-
+        protected override void SetupMocks()
+        {
             var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
+                               .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateSession(_connectionInfo))
-                .Returns(_sessionMock.Object);
+                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.CreateChannelSession()).Returns(_channelSessionMock.Object);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Open());
+            _remotePathTransformationMock.InSequence(sequence)
+                                         .Setup(p => p.Transform(_path))
+                                         .Returns(_transformedPath);
             _channelSessionMock.InSequence(sequence)
-                .Setup(p => p.SendExecRequest(string.Format("scp -t {0}", _quotedPath))).Returns(false);
+                               .Setup(p => p.SendExecRequest(string.Format("scp -t {0}", _transformedPath)))
+                               .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
 
             _scpClient = new ScpClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
             _scpClient.Connect();
         }
 
-        protected virtual void Act()
+        protected override void Act()
         {
             try
             {
@@ -96,7 +96,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void SendExecRequestOnChannelSessionShouldBeInvokedOnce()
         {
-            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t {0}", _quotedPath)), Times.Once);
+            _channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t {0}", _transformedPath)), Times.Once);
         }
 
         [TestMethod]

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

@@ -211,6 +211,7 @@
     <Compile Include="Classes\NetConfClientTest_Dispose_Disposed.cs" />
     <Compile Include="Classes\NetConfClientTest_Finalize_Connected.cs" />
     <Compile Include="Classes\PipeStreamTest_Dispose.cs" />
+    <Compile Include="Classes\ScpClientTestBase.cs" />
     <Compile Include="Classes\ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs" />
     <Compile Include="Classes\ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs" />
     <Compile Include="Classes\ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs" />

+ 5 - 4
src/Renci.SshNet/IServiceFactory.cs

@@ -64,13 +64,14 @@ namespace Renci.SshNet
         ISftpResponseFactory CreateSftpResponseFactory();
 
         /// <summary>
-        /// Creates an <see cref="IRemotePathTransformation"/>  that quotes a path in a way to be suitable
-        /// to be used with a shell.
+        /// Creates an <see cref="IRemotePathTransformation"/> that encloses a path in double quotes, and escapes
+        /// any embedded double quote with a backslash.
         /// </summary>
         /// <returns>
-        /// An <see cref="IRemotePathTransformation"/>  that quotes a path in a way to be suitable to be used
+        /// An <see cref="IRemotePathTransformation"/> that encloses a path in double quotes, and escapes any
+        /// embedded double quote with a backslash.
         /// with a shell.
         /// </returns>
-        IRemotePathTransformation CreateRemotePathQuoteTransformation();
+        IRemotePathTransformation CreateRemotePathDoubleQuoteTransformation();
     }
 }

+ 11 - 0
src/Renci.SshNet/PasswordAuthenticationMethod.cs

@@ -28,6 +28,17 @@ namespace Renci.SshNet
             get { return _requestMessage.MethodName; }
         }
 
+        /// <summary>
+        /// Gets the password as a sequence of bytes.
+        /// </summary>
+        /// <value>
+        /// The password as a sequence of bytes.
+        /// </value>
+        internal byte[] Password
+        {
+            get { return _password; }
+        }
+
         /// <summary>
         /// Occurs when user's password has expired and needs to be changed.
         /// </summary>

+ 76 - 0
src/Renci.SshNet/RemotePathDoubleQuoteTransformation.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Text;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// Encloses a path in double quotes, and escapes any embedded double quote with a backslash.
+    /// </summary>
+    internal class RemotePathDoubleQuoteTransformation : IRemotePathTransformation
+    {
+        /// <summary>
+        /// Encloses a path in double quotes, and escapes any embedded double quote with a backslash.
+        /// </summary>
+        /// <param name="path">The path to transform.</param>
+        /// <returns>
+        /// The transformed path.
+        /// </returns>
+        /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception>
+        /// <example>
+        /// <list type="table">
+        ///   <listheader>
+        ///     <term>Original</term>
+        ///     <term>Transformed</term>
+        ///   </listheader>
+        ///   <item>
+        ///     <term>/var/log/auth.log</term>
+        ///     <term>&quot;/var/log/auth.log&quot;</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>/var/mp3/Guns N' Roses</term>
+        ///     <term>&quot;/var/mp3/Guns N' Roses&quot;</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>/var/garbage!/temp</term>
+        ///     <term>&quot;/var/garbage!/temp&quot;</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>/var/would be 'kewl'!/not?</term>
+        ///     <term>&quot;/var/would be 'kewl'!, not?&quot;</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>!ignore!</term>
+        ///     <term>&quot;!ignore!&quot;</term>
+        ///   </item>
+        ///   <item>
+        ///     <term></term>
+        ///     <term>&quot;&quot;</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>Hello &quot;World&quot;</term>
+        ///     <term>&quot;Hello \&quot;World&quot;</term>
+        ///   </item>
+        /// </list>
+        /// </example>
+        public string Transform(string path)
+        {
+            if (path == null)
+            {
+                throw new ArgumentNullException("path");
+            }
+
+            var transformed = new StringBuilder(path.Length);
+
+            transformed.Append('"');
+            foreach (var c in path)
+            {
+                if (c == '"')
+                    transformed.Append('\\');
+                transformed.Append(c);
+            }
+            transformed.Append('"');
+
+            return transformed.ToString();
+        }
+    }
+}

+ 4 - 0
src/Renci.SshNet/RemotePathNoneTransformation.cs

@@ -12,6 +12,10 @@
         /// <returns>
         /// The specified path as is.
         /// </returns>
+        /// <remarks>
+        /// This transformation is recommended for servers that do not require any quoting to preserve the
+        /// literal value of metacharacters, or when paths are guaranteed to never contain any such characters.
+        /// </remarks>
         public string Transform(string path)
         {
             return path;

+ 46 - 10
src/Renci.SshNet/RemotePathQuoteTransformation.cs → src/Renci.SshNet/RemotePathShellQuoteTransformation.cs

@@ -4,12 +4,12 @@ using System.Text;
 namespace Renci.SshNet
 {
     /// <summary>
-    /// Quotes a path in a way to be suitable to be used with a shell.
+    /// Quotes a path in a way to be suitable to be used with a shell-based server.
     /// </summary>
-    internal class RemotePathQuoteTransformation : IRemotePathTransformation
+    internal class RemotePathShellQuoteTransformation : IRemotePathTransformation
     {
         /// <summary>
-        /// Quotes a path in a way to be suitable to be used with a shell.
+        /// Quotes a path in a way to be suitable to be used with a shell-based server.
         /// </summary>
         /// <param name="path">The path to transform.</param>
         /// <returns>
@@ -23,9 +23,13 @@ namespace Renci.SshNet
         /// pair of quotation marks.
         /// </para>
         /// <para>
-        /// If a shell command contains an exclamation mark (!), the C-Shell interprets it as a
-        /// meta-character for history substitution. This even works inside single-quotes or
-        /// quotation marks, unless escaped with a backslash (\).
+        /// An exclamation mark in <paramref name="path"/> is escaped with a backslash. This is
+        /// necessary because C Shell interprets it as a meta-character for history substitution
+        /// even when enclosed in single quotes or quotation marks.
+        /// </para>
+        /// <para>
+        /// All other characters are enclosed in single quotes. Sequences of such characters are grouped
+        /// in a single pair of single quotes.
         /// </para>
         /// <para>
         /// References:
@@ -42,10 +46,42 @@ namespace Renci.SshNet
         /// </list>
         /// </para>
         /// </remarks>
-        /// <returns>
-        /// The transformed path.
-        /// </returns>
-        /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception>
+        /// <example>
+        /// <list type="table">
+        ///   <listheader>
+        ///     <term>Original</term>
+        ///     <term>Transformed</term>
+        ///   </listheader>
+        ///   <item>
+        ///     <term>/var/log/auth.log</term>
+        ///     <term>'/var/log/auth.log'</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>/var/mp3/Guns N' Roses</term>
+        ///     <term>'/var/mp3/Guns N'"'"' Roses'</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>/var/garbage!/temp</term>
+        ///     <term>'/var/garbage'\!'/temp'</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>/var/would be 'kewl'!, not?</term>
+        ///     <term>'/var/would be '"'"'kewl'"'"\!', not?'</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>!ignore!</term>
+        ///     <term>\!'ignore'\!</term>
+        ///   </item>
+        ///   <item>
+        ///     <term></term>
+        ///     <term>''</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>Hello &quot;World&quot;</term>
+        ///     <term>'Hello "World"'</term>
+        ///   </item>
+        /// </list>
+        /// </example>
         public string Transform(string path)
         {
             if (path == null)

+ 72 - 20
src/Renci.SshNet/RemotePathTransformation.cs

@@ -5,32 +5,36 @@
     /// </summary>
     public static class RemotePathTransformation
     {
-        private static readonly IRemotePathTransformation QuoteTransformation = new RemotePathQuoteTransformation();
+        private static readonly IRemotePathTransformation ShellQuoteTransformation = new RemotePathShellQuoteTransformation();
         private static readonly IRemotePathTransformation NoneTransformation = new RemotePathNoneTransformation();
+        private static readonly IRemotePathTransformation DoubleQuoteTransformation = new RemotePathDoubleQuoteTransformation();
 
         /// <summary>
-        /// Quotes a path in a way to be suitable to be used with a shell.
+        /// Quotes a path in a way to be suitable to be used with a shell-based server.
         /// </summary>
+        /// <returns>
+        /// A quoted path.
+        /// </returns>
         /// <remarks>
         /// <para>
-        /// If the path contains a single-quote, that character is embedded in quotation marks.
+        /// If a path contains a single-quote, that character is embedded in quotation marks (eg. "'").
         /// Sequences of single-quotes are grouped in a single pair of quotation marks.
         /// </para>
         /// <para>
-        /// An exclamation mark (!) is escaped with a backslash, because the C shell would otherwise
-        /// interprete it as a meta-character for history substitution. It does this even if it's
-        /// enclosed in single-quotes or quotation marks, unless escaped with a backslash (\).
+        /// An exclamation mark in a path is escaped with a backslash. This is necessary because C Shell
+        /// interprets it as a meta-character for history substitution even when enclosed in single quotes
+        ///  or quotation marks.
         /// </para>
         /// <para>
-        /// All other character are enclosed in single-quotes, and grouped in a single pair of
-        /// single quotes where contiguous.
+        /// All other characters are enclosed in single quotes. Sequences of such characters are grouped
+        /// in a single pair of single quotes.
         /// </para>
         /// </remarks>
         /// <example>
         /// <list type="table">
         ///   <listheader>
         ///     <term>Original</term>
-        ///     <term>Quoted</term>
+        ///     <term>Transformed</term>
         ///   </listheader>
         ///   <item>
         ///     <term>/var/log/auth.log</term>
@@ -42,15 +46,11 @@
         ///   </item>
         ///   <item>
         ///     <term>/var/garbage!/temp</term>
-        ///     <term>'/var/garbage\!/temp'</term>
-        ///   </item>
-        ///   <item>
-        ///     <term>/var/garbage!/temp</term>
         ///     <term>'/var/garbage'\!'/temp'</term>
         ///   </item>
         ///   <item>
-        ///     <term>/var/would be 'kewl'!/not?</term>
-        ///     <term>'/var/would be '"'"'kewl'"'"\!'/not?'</term>
+        ///     <term>/var/would be 'kewl'!, not?</term>
+        ///     <term>'/var/would be '"'"'kewl'"'"\!', not?'</term>
         ///   </item>
         ///   <item>
         ///     <term>!ignore!</term>
@@ -60,24 +60,76 @@
         ///     <term></term>
         ///     <term>''</term>
         ///   </item>
+        ///   <item>
+        ///     <term>Hello &quot;World&quot;</term>
+        ///     <term>'Hello "World"'</term>
+        ///   </item>
         /// </list>
         /// </example>
-        public static IRemotePathTransformation Quote
+        public static IRemotePathTransformation ShellQuote
         {
-            get { return QuoteTransformation; }
+            get { return ShellQuoteTransformation; }
         }
 
         /// <summary>
         /// Performs no transformation.
         /// </summary>
         /// <remarks>
-        /// This transformation should be used for servers that do not support escape sequences in paths
-        /// or paths enclosed in quotes, or would preserve the escape characters or quotes in the path that
-        /// is handed off to the IO layer. This is recommended for servers that are not shell-based.
+        /// Recommended for servers that do not require any character to be escaped or enclosed in quotes,
+        /// or when paths are guaranteed to never contain any special characters (such as #, &quot;, ', $, ...).
         /// </remarks>
         public static IRemotePathTransformation None
         {
             get { return NoneTransformation; }
         }
+
+        /// <summary>
+        /// Encloses a path in double quotes, and escapes any embedded double quote with a backslash.
+        /// </summary>
+        /// <value>
+        /// A transformation that encloses a path in double quotes, and escapes any embedded double quote with
+        /// a backslash.
+        /// </value>
+        /// <example>
+        /// <list type="table">
+        ///   <listheader>
+        ///     <term>Original</term>
+        ///     <term>Transformed</term>
+        ///   </listheader>
+        ///   <item>
+        ///     <term>/var/log/auth.log</term>
+        ///     <term>&quot;/var/log/auth.log&quot;</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>/var/mp3/Guns N' Roses</term>
+        ///     <term>&quot;/var/mp3/Guns N' Roses&quot;</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>/var/garbage!/temp</term>
+        ///     <term>&quot;/var/garbage!/temp&quot;</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>/var/would be 'kewl'!/not?</term>
+        ///     <term>&quot;/var/would be 'kewl'!, not?&quot;</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>!ignore!</term>
+        ///     <term>&quot;!ignore!&quot;</term>
+        ///   </item>
+        ///   <item>
+        ///     <term></term>
+        ///     <term>&quot;&quot;</term>
+        ///   </item>
+        ///   <item>
+        ///     <term>Hello &quot;World&quot;</term>
+        ///     <term>&quot;Hello \&quot;World&quot;</term>
+        ///   </item>
+        /// </list>
+        /// </example>
+        public static IRemotePathTransformation DoubleQuote
+        {
+            get { return DoubleQuoteTransformation; }
+        }
+
     }
 }

+ 2 - 1
src/Renci.SshNet/Renci.SshNet.csproj

@@ -163,8 +163,9 @@
     <Compile Include="Messages\Transport\KeyExchangeEcdhInitMessage.cs" />
     <Compile Include="Messages\Transport\KeyExchangeEcdhReplyMessage.cs" />
     <Compile Include="Netconf\INetConfSession.cs" />
+    <Compile Include="RemotePathDoubleQuoteTransformation.cs" />
     <Compile Include="RemotePathNoneTransformation.cs" />
-    <Compile Include="RemotePathQuoteTransformation.cs" />
+    <Compile Include="RemotePathShellQuoteTransformation.cs" />
     <Compile Include="RemotePathTransformation.cs" />
     <Compile Include="Security\Cryptography\HMACMD5.cs" />
     <Compile Include="Security\Cryptography\HMACSHA1.cs" />

+ 9 - 3
src/Renci.SshNet/ScpClient.cs

@@ -56,12 +56,18 @@ namespace Renci.SshNet
         /// Gets or sets the transformation to apply to remote paths.
         /// </summary>
         /// <value>
-        /// The transformation to apply to remote paths. The default is <see cref="SshNet.RemotePathTransformation.Quote"/>.
+        /// The transformation to apply to remote paths. The default is <see cref="SshNet.RemotePathTransformation.DoubleQuote"/>.
         /// </value>
-        /// <exception cref="RemotePathTransformation"><paramref name="value"/> is <c>null</c>.</exception>
+        /// <exception cref="ArgumentNullException"><paramref name="value"/> is <c>null</c>.</exception>
         /// <remarks>
+        /// <para>
         /// This transformation is applied to the remote file or directory path that is passed to the
         /// <c>scp</c> command.
+        /// </para>
+        /// <para>
+        /// See <see cref="SshNet.RemotePathTransformation"/> for the transformations that are supplied
+        /// out-of-the-box with SSH.NET.
+        /// </para>
         /// </remarks>
         public IRemotePathTransformation RemotePathTransformation
         {
@@ -186,7 +192,7 @@ namespace Renci.SshNet
         {
             OperationTimeout = SshNet.Session.InfiniteTimeSpan;
             BufferSize = 1024 * 16;
-            _remotePathTransformation = SshNet.RemotePathTransformation.Quote;
+            _remotePathTransformation = serviceFactory.CreateRemotePathDoubleQuoteTransformation();
         }
 
         #endregion

+ 6 - 5
src/Renci.SshNet/ServiceFactory.cs

@@ -138,16 +138,17 @@ namespace Renci.SshNet
         }
 
         /// <summary>
-        /// Creates an <see cref="IRemotePathTransformation"/>  that quotes a path in a way to be suitable
-        /// to be used with a shell.
+        /// Creates an <see cref="IRemotePathTransformation"/> that encloses a path in double quotes, and escapes
+        /// any embedded double quote with a backslash.
         /// </summary>
         /// <returns>
-        /// An <see cref="IRemotePathTransformation"/>  that quotes a path in a way to be suitable to be used
+        /// An <see cref="IRemotePathTransformation"/> that encloses a path in double quotes, and escapes any
+        /// embedded double quote with a backslash.
         /// with a shell.
         /// </returns>
-        public IRemotePathTransformation CreateRemotePathQuoteTransformation()
+        public IRemotePathTransformation CreateRemotePathDoubleQuoteTransformation()
         {
-            return new RemotePathQuoteTransformation();
+            return RemotePathTransformation.DoubleQuote;
         }
     }
 }