diff --git a/src/Renci.SshNet/.editorconfig b/src/Renci.SshNet/.editorconfig
index 8dee1215..834fe9a6 100644
--- a/src/Renci.SshNet/.editorconfig
+++ b/src/Renci.SshNet/.editorconfig
@@ -192,6 +192,9 @@ dotnet_diagnostic.MA0040.severity = none
# duplicate of CA1849
dotnet_diagnostic.MA0042.severity = none
+# S1133: Deprecated code should be removed
+dotnet_diagnostic.S1133.severity = suggestion
+
# S3236: Caller information arguments should not be provided explicitly
dotnet_diagnostic.S3236.severity = none
diff --git a/src/Renci.SshNet/IRemotePathTransformation.cs b/src/Renci.SshNet/IRemotePathTransformation.cs
index d139db5d..735a3a1b 100644
--- a/src/Renci.SshNet/IRemotePathTransformation.cs
+++ b/src/Renci.SshNet/IRemotePathTransformation.cs
@@ -3,6 +3,22 @@
///
/// Represents a transformation that can be applied to a remote path.
///
+ ///
+ ///
+ /// A remote path transformation is used by to encode a remote path before it
+ /// is embedded in the scp command that is sent to the server. The correct transformation
+ /// depends on the server: a shell-based server requires the path to be quoted or escaped according to
+ /// its shell's rules, whereas a non-shell-based server uses the path literally and requires no
+ /// transformation (see ).
+ ///
+ ///
+ /// See for the implementations supplied with SSH.NET. On a
+ /// shell-based server, choosing a transformation that does not match the shell can leave a crafted
+ /// path able to execute as a command on the server; on a non-shell-based server, applying quoting or
+ /// escaping corrupts the path. A transformation should therefore be selected deliberately for the
+ /// target server and the trust placed in the supplied paths.
+ ///
+ ///
public interface IRemotePathTransformation
{
///
@@ -12,6 +28,7 @@
///
/// The transformed path.
///
+ /// is .
string Transform(string path);
}
}
diff --git a/src/Renci.SshNet/IServiceFactory.cs b/src/Renci.SshNet/IServiceFactory.cs
index 48ca70c8..6830c7ba 100644
--- a/src/Renci.SshNet/IServiceFactory.cs
+++ b/src/Renci.SshNet/IServiceFactory.cs
@@ -136,17 +136,6 @@ namespace Renci.SshNet
/// Client is not connected.
ShellStream CreateShellStreamNoTerminal(ISession session, int bufferSize);
- ///
- /// Creates an that encloses a path in double quotes, and escapes
- /// any embedded double quote with a backslash.
- ///
- ///
- /// An that encloses a path in double quotes, and escapes any
- /// embedded double quote with a backslash.
- /// with a shell.
- ///
- IRemotePathTransformation CreateRemotePathDoubleQuoteTransformation();
-
///
/// Creates an that can be used to establish a connection
/// to the server identified by the specified .
diff --git a/src/Renci.SshNet/ScpClient.cs b/src/Renci.SshNet/ScpClient.cs
index fffa9601..ac2d3496 100644
--- a/src/Renci.SshNet/ScpClient.cs
+++ b/src/Renci.SshNet/ScpClient.cs
@@ -29,10 +29,40 @@ namespace Renci.SshNet
///
///
///
+ ///
+ ///
+ /// SCP performs a transfer by running scp on the server with the remote path embedded in the
+ /// command. How that path must be encoded depends on the kind of server:
+ ///
+ /// -
+ ///
+ /// On a shell-based server the command is interpreted by a shell, so the path must be quoted or
+ /// escaped according to that shell's rules. An unsuitable transformation can allow a crafted path
+ /// to be executed as a command on the server.
+ ///
+ ///
+ /// -
+ ///
+ /// On a non-shell-based server the path is used literally and must not be quoted or escaped
+ /// (see ); otherwise the quoting or escape
+ /// characters end up as part of the file or directory path.
+ ///
+ ///
+ ///
+ /// Choose the supplied to the constructor to suit the remote
+ /// server and the trust you place in the paths you pass. Prefer , which does
+ /// not involve a remote shell, where possible.
+ ///
+ ///
///
#pragma warning disable MA0204 // Remove unnecessary partial modifier; not true for all targets
public partial class ScpClient : BaseClient
{
+ private const string ConstructorObsoleteMessage =
+ @"SCP with insufficiently-escaped paths can allow remote command injection. Use a constructor " +
+ "taking an IRemotePathTransformation which suits the escaping rules of the remote server and " +
+ "the trust environment in which this code runs, and consider using SFTP where possible.";
+
private const string FileInfoPattern = @"C(?\d{4}) (?\d+) (?.+)";
private const string DirectoryInfoPattern = @"D(?\d{4}) (?\d+) (?.+)";
private const string TimestampPattern = @"T(?\d+) 0 (?\d+) 0";
@@ -95,7 +125,9 @@ namespace Renci.SshNet
/// Gets or sets the transformation to apply to remote paths.
///
///
- /// The transformation to apply to remote paths. The default is .
+ /// The transformation to apply to remote paths. This is initialized from the transformation
+ /// passed to the constructor; the obsolete constructors that do not take one use
+ /// .
///
/// is .
///
@@ -122,6 +154,14 @@ namespace Renci.SshNet
}
}
+ private static IRemotePathTransformation DefaultTransform
+ {
+ get
+ {
+ return SshNet.RemotePathTransformation.DoubleQuote;
+ }
+ }
+
///
/// Gets or sets a value indicating whether the "-d" flag should be passed to the scp process on the server
/// when uploading files. Defaults to .
@@ -155,65 +195,105 @@ namespace Renci.SshNet
/// Initializes a new instance of the class.
///
/// The connection info.
+ /// The transformation to apply to remote paths.
/// is .
+ public ScpClient(ConnectionInfo connectionInfo, IRemotePathTransformation remotePathTransformation)
+ : this(connectionInfo, ownsConnectionInfo: false, remotePathTransformation)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Connection host.
+ /// Connection port.
+ /// Authentication username.
+ /// Authentication password.
+ /// The transformation to apply to remote paths.
+ /// is .
+ /// is invalid, or is or contains only whitespace characters.
+ /// is not within and .
+ public ScpClient(string host, int port, string username, string password, IRemotePathTransformation remotePathTransformation)
+ : this(new PasswordConnectionInfo(host, port, username, password), ownsConnectionInfo: true, remotePathTransformation)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Connection host.
+ /// Authentication username.
+ /// Authentication password.
+ /// The transformation to apply to remote paths.
+ /// is .
+ /// is invalid, or is or contains only whitespace characters.
+ public ScpClient(string host, string username, string password, IRemotePathTransformation remotePathTransformation)
+ : this(host, ConnectionInfo.DefaultPort, username, password, remotePathTransformation)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Connection host.
+ /// Connection port.
+ /// Authentication username.
+ /// The transformation to apply to remote paths.
+ /// Authentication private key file(s) .
+ /// is .
+ /// is invalid, -or- is or contains only whitespace characters.
+ /// is not within and .
+ public ScpClient(string host, int port, string username, IRemotePathTransformation remotePathTransformation, params IPrivateKeySource[] keyFiles)
+ : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true, remotePathTransformation)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Connection host.
+ /// Authentication username.
+ /// The transformation to apply to remote paths.
+ /// Authentication private key file(s) .
+ /// is .
+ /// is invalid, -or- is or contains only whitespace characters.
+ public ScpClient(string host, string username, IRemotePathTransformation remotePathTransformation, params IPrivateKeySource[] keyFiles)
+ : this(host, ConnectionInfo.DefaultPort, username, remotePathTransformation, keyFiles)
+ {
+ }
+
+ ///
+ [Obsolete(ConstructorObsoleteMessage)]
public ScpClient(ConnectionInfo connectionInfo)
- : this(connectionInfo, ownsConnectionInfo: false)
+ : this(connectionInfo, ownsConnectionInfo: false, DefaultTransform)
{
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// Connection host.
- /// Connection port.
- /// Authentication username.
- /// Authentication password.
- /// is .
- /// is invalid, or is or contains only whitespace characters.
- /// is not within and .
+ ///
+ [Obsolete(ConstructorObsoleteMessage)]
public ScpClient(string host, int port, string username, string password)
- : this(new PasswordConnectionInfo(host, port, username, password), ownsConnectionInfo: true)
+ : this(new PasswordConnectionInfo(host, port, username, password), ownsConnectionInfo: true, DefaultTransform)
{
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// Connection host.
- /// Authentication username.
- /// Authentication password.
- /// is .
- /// is invalid, or is or contains only whitespace characters.
+ ///
+ [Obsolete(ConstructorObsoleteMessage)]
public ScpClient(string host, string username, string password)
- : this(host, ConnectionInfo.DefaultPort, username, password)
+ : this(host, ConnectionInfo.DefaultPort, username, password, DefaultTransform)
{
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// Connection host.
- /// Connection port.
- /// Authentication username.
- /// Authentication private key file(s) .
- /// is .
- /// is invalid, -or- is or contains only whitespace characters.
- /// is not within and .
+ ///
+ [Obsolete(ConstructorObsoleteMessage)]
public ScpClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
- : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true)
+ : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true, DefaultTransform)
{
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// Connection host.
- /// Authentication username.
- /// Authentication private key file(s) .
- /// is .
- /// is invalid, -or- is or contains only whitespace characters.
+ ///
+ [Obsolete(ConstructorObsoleteMessage)]
public ScpClient(string host, string username, params IPrivateKeySource[] keyFiles)
- : this(host, ConnectionInfo.DefaultPort, username, keyFiles)
+ : this(host, ConnectionInfo.DefaultPort, username, DefaultTransform, keyFiles)
{
}
@@ -222,13 +302,14 @@ namespace Renci.SshNet
///
/// The connection info.
/// Specified whether this instance owns the connection info.
+ /// The transformation to apply to remote paths.
/// is .
///
/// If is , then the
/// connection info will be disposed when this instance is disposed.
///
- private ScpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo)
- : this(connectionInfo, ownsConnectionInfo, new ServiceFactory())
+ private ScpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IRemotePathTransformation remotePathTransformation)
+ : this(connectionInfo, ownsConnectionInfo, new ServiceFactory(), remotePathTransformation)
{
}
@@ -238,18 +319,19 @@ namespace Renci.SshNet
/// The connection info.
/// Specified whether this instance owns the connection info.
/// The factory to use for creating new services.
+ /// The transformation to apply to remote paths.
/// is .
/// is .
///
/// If is , then the
/// connection info will be disposed when this instance is disposed.
///
- internal ScpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory)
+ internal ScpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory, IRemotePathTransformation remotePathTransformation)
: base(connectionInfo, ownsConnectionInfo, serviceFactory)
{
OperationTimeout = Timeout.InfiniteTimeSpan;
BufferSize = 1024 * 16;
- _remotePathTransformation = serviceFactory.CreateRemotePathDoubleQuoteTransformation();
+ _remotePathTransformation = remotePathTransformation;
}
///
diff --git a/src/Renci.SshNet/ServiceFactory.cs b/src/Renci.SshNet/ServiceFactory.cs
index 937f4224..e560b433 100644
--- a/src/Renci.SshNet/ServiceFactory.cs
+++ b/src/Renci.SshNet/ServiceFactory.cs
@@ -139,20 +139,6 @@ namespace Renci.SshNet
return new ShellStream(session, bufferSize);
}
- ///
- /// Creates an that encloses a path in double quotes, and escapes
- /// any embedded double quote with a backslash.
- ///
- ///
- /// An that encloses a path in double quotes, and escapes any
- /// embedded double quote with a backslash.
- /// with a shell.
- ///
- public IRemotePathTransformation CreateRemotePathDoubleQuoteTransformation()
- {
- return RemotePathTransformation.DoubleQuote;
- }
-
///
/// Creates an that can be used to establish a connection
/// to the server identified by the specified .
diff --git a/test/Renci.SshNet.IntegrationBenchmarks/ScpClientBenchmark.cs b/test/Renci.SshNet.IntegrationBenchmarks/ScpClientBenchmark.cs
deleted file mode 100644
index f73643a9..00000000
--- a/test/Renci.SshNet.IntegrationBenchmarks/ScpClientBenchmark.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System.Text;
-
-using BenchmarkDotNet.Attributes;
-
-using Renci.SshNet.IntegrationTests.TestsFixtures;
-
-namespace Renci.SshNet.IntegrationBenchmarks
-{
- [MemoryDiagnoser]
- [SimpleJob]
- public class ScpClientBenchmark : IntegrationBenchmarkBase
- {
- private readonly InfrastructureFixture _infrastructureFixture;
-
- private readonly string _file = $"/tmp/{Guid.NewGuid()}.txt";
- private ScpClient? _scpClient;
- private MemoryStream? _uploadStream;
-
- public ScpClientBenchmark()
- {
- _infrastructureFixture = InfrastructureFixture.Instance;
- }
-
- [GlobalSetup]
- public async Task Setup()
- {
- await GlobalSetup().ConfigureAwait(false);
- _scpClient = new ScpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
- await _scpClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
-
- var fileContent = "File content !@#$%^&*()_+{}:,./<>[];'\\|";
- _uploadStream = new MemoryStream(Encoding.UTF8.GetBytes(fileContent));
- }
-
- [GlobalCleanup]
- public async Task Cleanup()
- {
- await GlobalCleanup().ConfigureAwait(false);
- await _uploadStream!.DisposeAsync().ConfigureAwait(false);
- }
-
- [Benchmark]
- public void Connect()
- {
- using var scpClient = new ScpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
- scpClient.Connect();
- }
-
- [Benchmark]
- public async Task ConnectAsync()
- {
- using var scpClient = new ScpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
- await scpClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
- }
-
- [Benchmark]
- public string ConnectUploadAndDownload()
- {
- using var scpClient = new ScpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
- scpClient.Connect();
- _uploadStream!.Position = 0;
- scpClient.Upload(_uploadStream, _file);
- using var downloadStream = new MemoryStream();
- scpClient.Download(_file, downloadStream);
-
- return Encoding.UTF8.GetString(downloadStream.ToArray());
- }
-
- [Benchmark]
- public string UploadAndDownload()
- {
- _uploadStream!.Position = 0;
- _scpClient!.Upload(_uploadStream, _file);
- using var downloadStream = new MemoryStream();
- _scpClient.Download(_file, downloadStream);
-
- return Encoding.UTF8.GetString(downloadStream.ToArray());
- }
- }
-}
diff --git a/test/Renci.SshNet.IntegrationTests/CompressionTests.cs b/test/Renci.SshNet.IntegrationTests/CompressionTests.cs
index fbceeae1..03e0f0c3 100644
--- a/test/Renci.SshNet.IntegrationTests/CompressionTests.cs
+++ b/test/Renci.SshNet.IntegrationTests/CompressionTests.cs
@@ -27,7 +27,7 @@ namespace Renci.SshNet.IntegrationTests
private void DoTest(KeyValuePair> compressor)
{
- using (var scpClient = new ScpClient(_connectionInfoFactory.Create()))
+ using (var scpClient = new ScpClient(_connectionInfoFactory.Create(), RemotePathTransformation.ShellQuote))
{
scpClient.ConnectionInfo.CompressionAlgorithms.Clear();
scpClient.ConnectionInfo.CompressionAlgorithms.Add(compressor);
diff --git a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/ScpClientTest.cs b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/ScpClientTest.cs
index 839e4a03..702139c0 100644
--- a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/ScpClientTest.cs
+++ b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/ScpClientTest.cs
@@ -2,6 +2,8 @@
using Renci.SshNet.Common;
+#pragma warning disable CS0618 // These SCP tests use the obsolete default-transformation constructors.
+
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
///
diff --git a/test/Renci.SshNet.IntegrationTests/RemoteSshdConfig.cs b/test/Renci.SshNet.IntegrationTests/RemoteSshdConfig.cs
index 3594af66..b3fb1342 100644
--- a/test/Renci.SshNet.IntegrationTests/RemoteSshdConfig.cs
+++ b/test/Renci.SshNet.IntegrationTests/RemoteSshdConfig.cs
@@ -16,7 +16,7 @@ namespace Renci.SshNet.IntegrationTests
_remoteSshd = remoteSshd;
_connectionInfoFactory = connectionInfoFactory;
- using (var client = new ScpClient(_connectionInfoFactory.Create()))
+ using (var client = new ScpClient(_connectionInfoFactory.Create(), RemotePathTransformation.ShellQuote))
{
client.Connect();
@@ -213,7 +213,7 @@ namespace Renci.SshNet.IntegrationTests
public RemoteSshd Update()
{
- using (var client = new ScpClient(_connectionInfoFactory.Create()))
+ using (var client = new ScpClient(_connectionInfoFactory.Create(), RemotePathTransformation.ShellQuote))
{
client.Connect();
diff --git a/test/Renci.SshNet.IntegrationTests/ScpClientTests.cs b/test/Renci.SshNet.IntegrationTests/ScpClientTests.cs
index ba424a24..d7c3f91f 100644
--- a/test/Renci.SshNet.IntegrationTests/ScpClientTests.cs
+++ b/test/Renci.SshNet.IntegrationTests/ScpClientTests.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS0618 // These SCP tests use the obsolete default-transformation constructors.
+
namespace Renci.SshNet.IntegrationTests
{
///
diff --git a/test/Renci.SshNet.IntegrationTests/ScpTests.cs b/test/Renci.SshNet.IntegrationTests/ScpTests.cs
index efd91c87..8724ae3d 100644
--- a/test/Renci.SshNet.IntegrationTests/ScpTests.cs
+++ b/test/Renci.SshNet.IntegrationTests/ScpTests.cs
@@ -1,5 +1,7 @@
using Renci.SshNet.Common;
+#pragma warning disable CS0618 // These SCP tests use the obsolete default-transformation constructors.
+
namespace Renci.SshNet.IntegrationTests
{
// TODO SCP: UPLOAD / DOWNLOAD ZERO LENGTH FILES
diff --git a/test/Renci.SshNet.IntegrationTests/SshTests.cs b/test/Renci.SshNet.IntegrationTests/SshTests.cs
index 07daf548..2c9dc565 100644
--- a/test/Renci.SshNet.IntegrationTests/SshTests.cs
+++ b/test/Renci.SshNet.IntegrationTests/SshTests.cs
@@ -789,7 +789,7 @@ namespace Renci.SshNet.IntegrationTests
{
const string hostsFile = "/etc/hosts";
- using (var client = new ScpClient(linuxAdminConnectionFactory.Create()))
+ using (var client = new ScpClient(linuxAdminConnectionFactory.Create(), RemotePathTransformation.ShellQuote))
{
client.Connect();
@@ -876,7 +876,7 @@ namespace Renci.SshNet.IntegrationTests
{
const string hostsFile = "/etc/hosts";
- using (var client = new ScpClient(linuxAdminConnectionFactory.Create()))
+ using (var client = new ScpClient(linuxAdminConnectionFactory.Create(), RemotePathTransformation.ShellQuote))
{
client.Connect();
diff --git a/test/Renci.SshNet.Tests/Classes/ScpClientTest.cs b/test/Renci.SshNet.Tests/Classes/ScpClientTest.cs
index 83bf8227..47362524 100644
--- a/test/Renci.SshNet.Tests/Classes/ScpClientTest.cs
+++ b/test/Renci.SshNet.Tests/Classes/ScpClientTest.cs
@@ -1,10 +1,12 @@
-using System;
+using System;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Tests.Common;
+#pragma warning disable CS0618 // These SCP tests use the obsolete default-transformation constructors.
+
namespace Renci.SshNet.Tests.Classes
{
///
@@ -22,13 +24,17 @@ namespace Renci.SshNet.Tests.Classes
}
[TestMethod]
- public void Ctor_ConnectionInfo_Null()
+ [DataRow(false)]
+ [DataRow(true)]
+ public void Ctor_ConnectionInfo_Null(bool remoteTransformCtor)
{
const ConnectionInfo connectionInfo = null;
try
{
- _ = new ScpClient(connectionInfo);
+ _ = remoteTransformCtor
+ ? new ScpClient(connectionInfo, RemotePathTransformation.ShellQuote)
+ : new ScpClient(connectionInfo);
Assert.Fail();
}
catch (ArgumentNullException ex)
@@ -39,35 +45,59 @@ namespace Renci.SshNet.Tests.Classes
}
[TestMethod]
- public void Ctor_ConnectionInfo_NotNull()
+ [DataRow(false)]
+ [DataRow(true)]
+ public void Ctor_ConnectionInfo_NotNull(bool remoteTransformCtor)
{
var connectionInfo = new ConnectionInfo("HOST", "USER", new PasswordAuthenticationMethod("USER", "PWD"));
- var client = new ScpClient(connectionInfo);
+ ScpClient client;
+ if (remoteTransformCtor)
+ {
+ client = new ScpClient(connectionInfo, RemotePathTransformation.ShellQuote);
+ Assert.AreSame(RemotePathTransformation.ShellQuote, client.RemotePathTransformation);
+ }
+ else
+ {
+ client = new ScpClient(connectionInfo);
+ Assert.AreSame(RemotePathTransformation.DoubleQuote, client.RemotePathTransformation);
+ }
+
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()
+ [DataRow(false)]
+ [DataRow(true)]
+ public void Ctor_HostAndPortAndUsernameAndPassword(bool remoteTransformCtor)
{
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);
+ ScpClient client;
+ if (remoteTransformCtor)
+ {
+ client = new ScpClient(host, port, userName, password, RemotePathTransformation.ShellQuote);
+ Assert.AreSame(RemotePathTransformation.ShellQuote, client.RemotePathTransformation);
+ }
+ else
+ {
+ client = new ScpClient(host, port, userName, password);
+ Assert.AreSame(RemotePathTransformation.DoubleQuote, client.RemotePathTransformation);
+ }
+
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;
@@ -85,19 +115,31 @@ namespace Renci.SshNet.Tests.Classes
}
[TestMethod]
- public void Ctor_HostAndUsernameAndPassword()
+ [DataRow(false)]
+ [DataRow(true)]
+ public void Ctor_HostAndUsernameAndPassword(bool remoteTransformCtor)
{
var host = _random.Next().ToString();
var userName = _random.Next().ToString();
var password = _random.Next().ToString();
- var client = new ScpClient(host, userName, password);
+ ScpClient client;
+ if (remoteTransformCtor)
+ {
+ client = new ScpClient(host, userName, password, RemotePathTransformation.ShellQuote);
+ Assert.AreSame(RemotePathTransformation.ShellQuote, client.RemotePathTransformation);
+ }
+ else
+ {
+ client = new ScpClient(host, userName, password);
+ Assert.AreSame(RemotePathTransformation.DoubleQuote, client.RemotePathTransformation);
+ }
+
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;
@@ -115,20 +157,32 @@ namespace Renci.SshNet.Tests.Classes
}
[TestMethod]
- public void Ctor_HostAndPortAndUsernameAndPrivateKeys()
+ [DataRow(false)]
+ [DataRow(true)]
+ public void Ctor_HostAndPortAndUsernameAndPrivateKeys(bool remoteTransformCtor)
{
var host = _random.Next().ToString();
var port = _random.Next(1, 100);
var userName = _random.Next().ToString();
var privateKeys = new[] { GetRsaKey(), GetEcdsaKey() };
- var client = new ScpClient(host, port, userName, privateKeys);
+ ScpClient client;
+ if (remoteTransformCtor)
+ {
+ client = new ScpClient(host, port, userName, RemotePathTransformation.ShellQuote, privateKeys);
+ Assert.AreSame(RemotePathTransformation.ShellQuote, client.RemotePathTransformation);
+ }
+ else
+ {
+ client = new ScpClient(host, port, userName, privateKeys);
+ Assert.AreSame(RemotePathTransformation.DoubleQuote, client.RemotePathTransformation);
+ }
+
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;
@@ -149,19 +203,31 @@ namespace Renci.SshNet.Tests.Classes
}
[TestMethod]
- public void Ctor_HostAndUsernameAndPrivateKeys()
+ [DataRow(false)]
+ [DataRow(true)]
+ public void Ctor_HostAndUsernameAndPrivateKeys(bool remoteTransformCtor)
{
var host = _random.Next().ToString();
var userName = _random.Next().ToString();
var privateKeys = new[] { GetRsaKey(), GetEcdsaKey() };
- var client = new ScpClient(host, userName, privateKeys);
+ ScpClient client;
+ if (remoteTransformCtor)
+ {
+ client = new ScpClient(host, userName, RemotePathTransformation.ShellQuote, privateKeys);
+ Assert.AreSame(RemotePathTransformation.ShellQuote, client.RemotePathTransformation);
+ }
+ else
+ {
+ client = new ScpClient(host, userName, privateKeys);
+ Assert.AreSame(RemotePathTransformation.DoubleQuote, client.RemotePathTransformation);
+ }
+
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;
diff --git a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs
index 7e6f1aeb..9c0248bc 100644
--- a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs
+++ b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs
@@ -37,9 +37,6 @@ namespace Renci.SshNet.Tests.Classes
{
var sequence = new MockSequence();
- ServiceFactoryMock.InSequence(sequence)
- .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
- .Returns(_remotePathTransformationMock.Object);
ServiceFactoryMock.InSequence(sequence)
.Setup(p => p.CreateSocketFactory())
.Returns(SocketFactoryMock.Object);
@@ -64,7 +61,7 @@ namespace Renci.SshNet.Tests.Classes
{
base.Arrange();
- _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object);
+ _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object, _remotePathTransformationMock.Object);
_scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
_scpClient.Connect();
}
diff --git a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs
index ca638f46..c85f8afb 100644
--- a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs
+++ b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs
@@ -37,9 +37,6 @@ namespace Renci.SshNet.Tests.Classes
{
var sequence = new MockSequence();
- ServiceFactoryMock.InSequence(sequence)
- .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
- .Returns(_remotePathTransformationMock.Object);
ServiceFactoryMock.InSequence(sequence)
.Setup(p => p.CreateSocketFactory())
.Returns(SocketFactoryMock.Object);
@@ -63,7 +60,7 @@ namespace Renci.SshNet.Tests.Classes
{
base.Arrange();
- _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object);
+ _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object, _remotePathTransformationMock.Object);
_scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
_scpClient.Connect();
}
diff --git a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs
index 0c8d1d7a..8fc083b3 100644
--- a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs
+++ b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs
@@ -37,9 +37,6 @@ namespace Renci.SshNet.Tests.Classes
{
var sequence = new MockSequence();
- _ = ServiceFactoryMock.InSequence(sequence)
- .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
- .Returns(_remotePathTransformationMock.Object);
_ = ServiceFactoryMock.InSequence(sequence)
.Setup(p => p.CreateSocketFactory())
.Returns(SocketFactoryMock.Object);
@@ -72,7 +69,7 @@ namespace Renci.SshNet.Tests.Classes
{
base.Arrange();
- _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object);
+ _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object, _remotePathTransformationMock.Object);
_scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
_scpClient.Connect();
}
diff --git a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs
index 68001f51..932c72d1 100644
--- a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs
+++ b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs
@@ -36,9 +36,6 @@ namespace Renci.SshNet.Tests.Classes
{
var sequence = new MockSequence();
- ServiceFactoryMock.InSequence(sequence)
- .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
- .Returns(_remotePathTransformationMock.Object);
ServiceFactoryMock.InSequence(sequence)
.Setup(p => p.CreateSocketFactory())
.Returns(SocketFactoryMock.Object);
@@ -63,7 +60,7 @@ namespace Renci.SshNet.Tests.Classes
{
base.Arrange();
- _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object);
+ _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object, _remotePathTransformationMock.Object);
_scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
_scpClient.Connect();
}
diff --git a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs
index 8d677f1f..5ad84a46 100644
--- a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs
+++ b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs
@@ -42,9 +42,6 @@ namespace Renci.SshNet.Tests.Classes
{
var sequence = new MockSequence();
- ServiceFactoryMock.InSequence(sequence)
- .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
- .Returns(_remotePathTransformationMock.Object);
ServiceFactoryMock.InSequence(sequence)
.Setup(p => p.CreateSocketFactory())
.Returns(SocketFactoryMock.Object);
@@ -69,7 +66,7 @@ namespace Renci.SshNet.Tests.Classes
{
base.Arrange();
- _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object);
+ _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object, _remotePathTransformationMock.Object);
_scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
_scpClient.Connect();
}
diff --git a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs
index 63610f34..350ef1b8 100644
--- a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs
+++ b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs
@@ -49,9 +49,6 @@ namespace Renci.SshNet.Tests.Classes
{
var sequence = new MockSequence();
- _ = ServiceFactoryMock.InSequence(sequence)
- .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
- .Returns(_remotePathTransformationMock.Object);
_ = ServiceFactoryMock.InSequence(sequence)
.Setup(p => p.CreateSocketFactory())
.Returns(SocketFactoryMock.Object);
@@ -106,7 +103,7 @@ namespace Renci.SshNet.Tests.Classes
{
base.Arrange();
- _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object)
+ _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object, _remotePathTransformationMock.Object)
{
BufferSize = (uint)_bufferSize
};
diff --git a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs
index 2800b88c..f0b2f17f 100644
--- a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs
+++ b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs
@@ -40,9 +40,6 @@ namespace Renci.SshNet.Tests.Classes
{
var sequence = new MockSequence();
- _ = ServiceFactoryMock.InSequence(sequence)
- .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
- .Returns(_remotePathTransformationMock.Object);
_ = ServiceFactoryMock.InSequence(sequence)
.Setup(p => p.CreateSocketFactory())
.Returns(SocketFactoryMock.Object);
@@ -75,7 +72,7 @@ namespace Renci.SshNet.Tests.Classes
{
base.Arrange();
- _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object);
+ _scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object, _remotePathTransformationMock.Object);
_scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
_scpClient.Connect();
}