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(); }