diff --git a/src/Renci.SshNet.IntegrationTests/.gitignore b/src/Renci.SshNet.IntegrationTests/.gitignore new file mode 100644 index 000000000..0819dad73 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/.gitignore @@ -0,0 +1 @@ +TestResults/ \ No newline at end of file diff --git a/src/Renci.SshNet.IntegrationTests/App.config b/src/Renci.SshNet.IntegrationTests/App.config new file mode 100644 index 000000000..c9794e84d --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/App.config @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Renci.SshNet.IntegrationTests/AuthenticationMethodFactory.cs b/src/Renci.SshNet.IntegrationTests/AuthenticationMethodFactory.cs new file mode 100644 index 000000000..638b285d8 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/AuthenticationMethodFactory.cs @@ -0,0 +1,84 @@ +namespace Renci.SshNet.IntegrationTests +{ + public class AuthenticationMethodFactory + { + public PasswordAuthenticationMethod CreatePowerUserPasswordAuthenticationMethod() + { + var user = Users.Admin; + return new PasswordAuthenticationMethod(user.UserName, user.Password); + } + + public PrivateKeyAuthenticationMethod CreateRegularUserPrivateKeyAuthenticationMethod() + { + var privateKeyFile = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa"); + return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile); + } + + public PrivateKeyAuthenticationMethod CreateRegularUserMultiplePrivateKeyAuthenticationMethod() + { + var privateKeyFile1 = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa"); + var privateKeyFile2 = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa"); + return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile1, privateKeyFile2); + } + + public PrivateKeyAuthenticationMethod CreateRegularUserPrivateKeyWithPassPhraseAuthenticationMethod() + { + var privateKeyFile = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa_with_pass", "tester"); + return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile); + } + + public PrivateKeyAuthenticationMethod CreateRegularUserPrivateKeyWithEmptyPassPhraseAuthenticationMethod() + { + var privateKeyFile = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa_with_pass", null); + return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile); + } + + public PrivateKeyAuthenticationMethod CreateRegularUserPrivateKeyAuthenticationMethodWithBadKey() + { + var privateKeyFile = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_noaccess.rsa"); + return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile); + } + + public PasswordAuthenticationMethod CreateRegulatUserPasswordAuthenticationMethod() + { + return new PasswordAuthenticationMethod(Users.Regular.UserName, Users.Regular.Password); + } + + public PasswordAuthenticationMethod CreateRegularUserPasswordAuthenticationMethodWithBadPassword() + { + return new PasswordAuthenticationMethod(Users.Regular.UserName, "xxx"); + } + + public KeyboardInteractiveAuthenticationMethod CreateRegularUserKeyboardInteractiveAuthenticationMethod() + { + var keyboardInteractive = new KeyboardInteractiveAuthenticationMethod(Users.Regular.UserName); + keyboardInteractive.AuthenticationPrompt += (sender, args) => + { + foreach (var authenticationPrompt in args.Prompts) + { + authenticationPrompt.Response = Users.Regular.Password; + } + }; + return keyboardInteractive; + } + + private PrivateKeyFile GetPrivateKey(string resourceName, string passPhrase = null) + { + using (var stream = GetResourceStream(resourceName)) + { + return new PrivateKeyFile(stream, passPhrase); + } + } + + private Stream GetResourceStream(string resourceName) + { + var type = GetType(); + var resourceStream = type.Assembly.GetManifestResourceStream(resourceName); + if (resourceStream == null) + { + throw new ArgumentException($"Resource '{resourceName}' not found in assembly '{type.Assembly.FullName}'.", nameof(resourceName)); + } + return resourceStream; + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/AuthenticationTests.cs b/src/Renci.SshNet.IntegrationTests/AuthenticationTests.cs new file mode 100644 index 000000000..c3968e949 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/AuthenticationTests.cs @@ -0,0 +1,427 @@ +using Renci.SshNet.Common; +using Renci.SshNet.IntegrationTests.Common; + +namespace Renci.SshNet.IntegrationTests +{ + [TestClass] + public class AuthenticationTests : IntegrationTestBase + { + private AuthenticationMethodFactory _authenticationMethodFactory; + private IConnectionInfoFactory _connectionInfoFactory; + private IConnectionInfoFactory _adminConnectionInfoFactory; + private RemoteSshdConfig _remoteSshdConfig; + + [TestInitialize] + public void SetUp() + { + _authenticationMethodFactory = new AuthenticationMethodFactory(); + _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort, _authenticationMethodFactory); + _adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort); + _remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig(); + } + + [TestCleanup] + public void TearDown() + { + _remoteSshdConfig?.Reset(); + + using (var client = new SshClient(_adminConnectionInfoFactory.Create())) + { + client.Connect(); + + // Reset the password back to the "regular" password. + using (var cmd = client.RunCommand($"echo \"{Users.Regular.Password}\n{Users.Regular.Password}\" | sudo passwd " + Users.Regular.UserName)) + { + Assert.AreEqual(0, cmd.ExitStatus, cmd.Error); + } + + // Remove password expiration + using (var cmd = client.RunCommand($"sudo chage --expiredate -1 " + Users.Regular.UserName)) + { + Assert.AreEqual(0, cmd.ExitStatus, cmd.Error); + } + } + } + + [TestMethod] + public void Multifactor_PublicKey() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + } + + [TestMethod] + [TestCategory("Authentication")] + public void Multifactor_PublicKey_Connect_Then_Reconnect() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + client.Disconnect(); + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void Multifactor_PublicKeyWithPassPhrase() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyWithPassPhraseAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + } + + [TestMethod] + [ExpectedException(typeof(SshPassPhraseNullOrEmptyException))] + public void Multifactor_PublicKeyWithEmptyPassPhrase() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyWithEmptyPassPhraseAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + } + + [TestMethod] + public void Multifactor_PublicKey_MultiplePrivateKey() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserMultiplePrivateKeyAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + } + + [TestMethod] + public void Multifactor_PublicKey_MultipleAuthenticationMethod() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod(), + _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + } + + [TestMethod] + public void Multifactor_KeyboardInteractiveAndPublicKey() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive,publickey") + .WithChallengeResponseAuthentication(true) + .WithKeyboardInteractiveAuthentication(true) + .WithUsePAM(true) + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPasswordAuthenticationMethodWithBadPassword(), + _authenticationMethodFactory.CreateRegularUserKeyboardInteractiveAuthenticationMethod(), + _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + } + + [TestMethod] + public void Multifactor_Password_ExceedsPartialSuccessLimit() + { + // configure server to require more successfull authentications from a given method than our partial + // success limit (5) allows + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,password,password,password,password,password") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + try + { + client.Connect(); + Assert.Fail(); + } + catch (SshAuthenticationException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("Reached authentication attempt limit for method (password).", ex.Message); + } + } + } + + [TestMethod] + public void Multifactor_Password_MatchPartialSuccessLimit() + { + // configure server to require a number of successfull authentications from a given method that exactly + // matches our partial success limit (5) + + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,password,password,password,password") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + } + + [TestMethod] + public void Multifactor_Password_Or_PublicKeyAndKeyboardInteractive() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password publickey,keyboard-interactive") + .WithChallengeResponseAuthentication(true) + .WithKeyboardInteractiveAuthentication(true) + .WithUsePAM(true) + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod(), + _authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + } + + [TestMethod] + public void Multifactor_Password_Or_PublicKeyAndPassword_BadPassword() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password publickey,password") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPasswordAuthenticationMethodWithBadPassword(), + _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + try + { + client.Connect(); + Assert.Fail(); + } + catch (SshAuthenticationException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("Permission denied (password).", ex.Message); + } + } + } + + [TestMethod] + public void Multifactor_PasswordAndPublicKey_Or_PasswordAndPassword() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,publickey password,password") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod(), + _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethodWithBadKey()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + + connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPasswordAuthenticationMethodWithBadPassword(), + _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + try + { + client.Connect(); + Assert.Fail(); + } + catch (SshAuthenticationException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("Permission denied (password).", ex.Message); + } + } + + } + + [TestMethod] + public void Multifactor_PasswordAndPassword_Or_PublicKey() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,password publickey") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod(), + _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethodWithBadKey()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + + connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + + } + + [TestMethod] + public void Multifactor_Password_Or_Password() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password password") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + + connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod(), + _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethodWithBadKey()); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + } + } + + [TestMethod] + public void KeyboardInteractive_PasswordExpired() + { + var temporaryPassword = new Random().Next().ToString(); + + using (var client = new SshClient(_adminConnectionInfoFactory.Create())) + { + client.Connect(); + + // Temporarity modify password so that when we expire this password, we change reset the password back to + // the "regular" password. + using (var cmd = client.RunCommand($"echo \"{temporaryPassword}\n{temporaryPassword}\" | sudo passwd " + Users.Regular.UserName)) + { + Assert.AreEqual(0, cmd.ExitStatus, cmd.Error); + } + + // Force the password to expire immediately + using (var cmd = client.RunCommand($"sudo chage -d 0 " + Users.Regular.UserName)) + { + Assert.AreEqual(0, cmd.ExitStatus, cmd.Error); + } + } + + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive") + .WithChallengeResponseAuthentication(true) + .WithKeyboardInteractiveAuthentication(true) + .WithUsePAM(true) + .Update() + .Restart(); + + var keyboardInteractive = new KeyboardInteractiveAuthenticationMethod(Users.Regular.UserName); + int authenticationPromptCount = 0; + keyboardInteractive.AuthenticationPrompt += (sender, args) => + { + Console.WriteLine(args.Instruction); + foreach (var authenticationPrompt in args.Prompts) + { + Console.WriteLine(authenticationPrompt.Request); + switch (authenticationPromptCount) + { + case 0: + // Regular password prompt + authenticationPrompt.Response = temporaryPassword; + break; + case 1: + // Password expired, provide current password + authenticationPrompt.Response = temporaryPassword; + break; + case 2: + // Password expired, provide new password + authenticationPrompt.Response = Users.Regular.Password; + break; + case 3: + // Password expired, retype new password + authenticationPrompt.Response = Users.Regular.Password; + break; + } + + authenticationPromptCount++; + } + }; + + var connectionInfo = _connectionInfoFactory.Create(keyboardInteractive); + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + Assert.AreEqual(4, authenticationPromptCount); + } + } + + [TestMethod] + public void KeyboardInteractiveConnectionInfo() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive") + .WithChallengeResponseAuthentication(true) + .WithKeyboardInteractiveAuthentication(true) + .WithUsePAM(true) + .Update() + .Restart(); + + var host = SshServerHostName; + var port = SshServerPort; + var username = User.UserName; + var password = User.Password; + + #region Example KeyboardInteractiveConnectionInfo AuthenticationPrompt + + var connectionInfo = new KeyboardInteractiveConnectionInfo(host, port, username); + connectionInfo.AuthenticationPrompt += delegate (object sender, AuthenticationPromptEventArgs e) + { + Console.WriteLine(e.Instruction); + + foreach (var prompt in e.Prompts) + { + Console.WriteLine(prompt.Request); + prompt.Response = password; + } + }; + + using (var client = new SftpClient(connectionInfo)) + { + client.Connect(); + + // Do something here + client.Disconnect(); + } + + #endregion + + Assert.AreEqual(connectionInfo.Host, SshServerHostName); + Assert.AreEqual(connectionInfo.Username, User.UserName); + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/Common/ArrayBuilder.cs b/src/Renci.SshNet.IntegrationTests/Common/ArrayBuilder.cs new file mode 100644 index 000000000..1720c19c9 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/Common/ArrayBuilder.cs @@ -0,0 +1,32 @@ +namespace Renci.SshNet.IntegrationTests.Common +{ + public class ArrayBuilder + { + private readonly List _buffer; + + public ArrayBuilder() + { + _buffer = new List(); + } + + public ArrayBuilder Add(T[] array) + { + return Add(array, 0, array.Length); + } + + public ArrayBuilder Add(T[] array, int index, int length) + { + for (var i = 0; i < length; i++) + { + _buffer.Add(array[index + i]); + } + + return this; + } + + public T[] Build() + { + return _buffer.ToArray(); + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/Common/AsyncSocketListener.cs b/src/Renci.SshNet.IntegrationTests/Common/AsyncSocketListener.cs new file mode 100644 index 000000000..753385582 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/Common/AsyncSocketListener.cs @@ -0,0 +1,393 @@ +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.IntegrationTests.Common +{ + public class AsyncSocketListener : IDisposable + { + private readonly IPEndPoint _endPoint; + private readonly ManualResetEvent _acceptCallbackDone; + private readonly List _connectedClients; + private readonly object _syncLock; + private Socket _listener; + private Thread _receiveThread; + private bool _started; + private string _stackTrace; + + public delegate void BytesReceivedHandler(byte[] bytesReceived, Socket socket); + public delegate void ConnectedHandler(Socket socket); + + public event BytesReceivedHandler BytesReceived; + public event ConnectedHandler Connected; + public event ConnectedHandler Disconnected; + + public AsyncSocketListener(IPEndPoint endPoint) + { + _endPoint = endPoint; + _acceptCallbackDone = new ManualResetEvent(false); + _connectedClients = new List(); + _syncLock = new object(); + ShutdownRemoteCommunicationSocket = true; + } + + /// + /// Gets a value indicating whether the is invoked on the + /// that is used to handle the communication with the remote host, when the remote host has closed the connection. + /// + /// + /// to invoke on the that is used + /// to handle the communication with the remote host, when the remote host has closed the connection; otherwise, + /// . The default is . + /// + public bool ShutdownRemoteCommunicationSocket { get; set; } + + public void Start() + { + _listener = new Socket(_endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + _listener.Bind(_endPoint); + _listener.Listen(1); + + _started = true; + + _receiveThread = new Thread(StartListener); + _receiveThread.Start(_listener); + + _stackTrace = Environment.StackTrace; + } + + public void Stop() + { + _started = false; + + lock (_syncLock) + { + foreach (var connectedClient in _connectedClients) + { + try + { + connectedClient.Shutdown(SocketShutdown.Send); + } + catch (Exception ex) + { + Console.Error.WriteLine("[{0}] Failure shutting down socket: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } + + DrainSocket(connectedClient); + connectedClient.Dispose(); + } + + _connectedClients.Clear(); + } + + _listener?.Dispose(); + + if (_receiveThread != null) + { + _receiveThread.Join(); + _receiveThread = null; + } + } + + public void Dispose() + { + Stop(); + GC.SuppressFinalize(this); + } + + private void StartListener(object state) + { + try + { + var listener = (Socket) state; + while (_started) + { + _ = _acceptCallbackDone.Reset(); + _ = listener.BeginAccept(AcceptCallback, listener); + _ = _acceptCallbackDone.WaitOne(); + } + } + catch (Exception ex) + { + // On .NET framework when Thread throws an exception then unit tests + // were executed without any problem. + // On new .NET exceptions from Thread breaks unit tests session. + Console.Error.WriteLine("[{0}] Failure in StartListener: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } + } + + private void AcceptCallback(IAsyncResult ar) + { + // Signal the main thread to continue + _ = _acceptCallbackDone.Set(); + + // Get the socket that listens for inbound connections + var listener = (Socket) ar.AsyncState; + + // Get the socket that handles the client request + Socket handler; + + try + { + handler = listener.EndAccept(ar); + } + catch (SocketException ex) + { + // The listener is stopped through a Dispose() call, which in turn causes + // Socket.EndAccept(...) to throw a SocketException or + // ObjectDisposedException + // + // Since we consider such an exception normal when the listener is being + // stopped, we only write a message to stderr if the listener is considered + // to be up and running + if (_started) + { + Console.Error.WriteLine("[{0}] Failure accepting new connection: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } + return; + } + catch (ObjectDisposedException ex) + { + // The listener is stopped through a Dispose() call, which in turn causes + // Socket.EndAccept(IAsyncResult) to throw a SocketException or + // ObjectDisposedException + // + // Since we consider such an exception normal when the listener is being + // stopped, we only write a message to stderr if the listener is considered + // to be up and running + if (_started) + { + Console.Error.WriteLine("[{0}] Failure accepting new connection: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } + return; + } + + // Signal new connection + SignalConnected(handler); + + lock (_syncLock) + { + // Register client socket + _connectedClients.Add(handler); + } + + var state = new SocketStateObject(handler); + + try + { + _ = handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state); + } + catch (SocketException ex) + { + // The listener is stopped through a Dispose() call, which in turn causes + // Socket.BeginReceive(...) to throw a SocketException or + // ObjectDisposedException + // + // Since we consider such an exception normal when the listener is being + // stopped, we only write a message to stderr if the listener is considered + // to be up and running + if (_started) + { + Console.Error.WriteLine("[{0}] Failure receiving new data: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } + } + catch (ObjectDisposedException ex) + { + // The listener is stopped through a Dispose() call, which in turn causes + // Socket.BeginReceive(...) to throw a SocketException or + // ObjectDisposedException + // + // Since we consider such an exception normal when the listener is being + // stopped, we only write a message to stderr if the listener is considered + // to be up and running + if (_started) + { + Console.Error.WriteLine("[{0}] Failure receiving new data: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } + } + } + + private void ReadCallback(IAsyncResult ar) + { + // Retrieve the state object and the handler socket + // from the asynchronous state object + var state = (SocketStateObject) ar.AsyncState; + var handler = state.Socket; + + int bytesRead; + try + { + // Read data from the client socket. + bytesRead = handler.EndReceive(ar, out var errorCode); + if (errorCode != SocketError.Success) + { + bytesRead = 0; + } + } + catch (SocketException ex) + { + // The listener is stopped through a Dispose() call, which in turn causes + // Socket.EndReceive(...) to throw a SocketException or + // ObjectDisposedException + // + // Since we consider such an exception normal when the listener is being + // stopped, we only write a message to stderr if the listener is considered + // to be up and running + if (_started) + { + Console.Error.WriteLine("[{0}] Failure receiving new data: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } + return; + } + catch (ObjectDisposedException ex) + { + // The listener is stopped through a Dispose() call, which in turn causes + // Socket.EndReceive(...) to throw a SocketException or + // ObjectDisposedException + // + // Since we consider such an exception normal when the listener is being + // stopped, we only write a message to stderr if the listener is considered + // to be up and running + if (_started) + { + Console.Error.WriteLine("[{0}] Failure receiving new data: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } + return; + } + + void ConnectionDisconnected() + { + SignalDisconnected(handler); + + if (ShutdownRemoteCommunicationSocket) + { + lock (_syncLock) + { + if (!_started) + { + return; + } + + try + { + handler.Shutdown(SocketShutdown.Send); + handler.Close(); + } + catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset) + { + // On .NET 7 we got Socker Exception with ConnectionReset from Shutdown method + // when the socket is disposed + } + catch (SocketException ex) + { + throw new Exception("Exception in ReadCallback: " + ex.SocketErrorCode + " " + _stackTrace, ex); + } + catch (Exception ex) + { + throw new Exception("Exception in ReadCallback: " + _stackTrace, ex); + } + + _ = _connectedClients.Remove(handler); + } + } + } + + if (bytesRead > 0) + { + var bytesReceived = new byte[bytesRead]; + Array.Copy(state.Buffer, bytesReceived, bytesRead); + SignalBytesReceived(bytesReceived, handler); + + try + { + _ = handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state); + } + catch (ObjectDisposedException) + { + // TODO On .NET 7, sometimes we get ObjectDisposedException when _started but only on appveyor, locally it works + ConnectionDisconnected(); + } + catch (SocketException ex) + { + if (!_started) + { + throw new Exception("BeginReceive while stopping!", ex); + } + + throw new Exception("BeginReceive while started!: " + ex.SocketErrorCode + " " + _stackTrace, ex); + } + + } + else + { + ConnectionDisconnected(); + } + } + + private void SignalBytesReceived(byte[] bytesReceived, Socket client) + { + BytesReceived?.Invoke(bytesReceived, client); + } + + private void SignalConnected(Socket client) + { + Connected?.Invoke(client); + } + + private void SignalDisconnected(Socket client) + { + Disconnected?.Invoke(client); + } + + private static void DrainSocket(Socket socket) + { + var buffer = new byte[128]; + + try + { + while (true && socket.Connected) + { + var bytesRead = socket.Receive(buffer); + if (bytesRead == 0) + { + break; + } + } + } + catch (SocketException ex) + { + Console.Error.WriteLine("[{0}] Failure draining socket ({1}): {2}", + typeof(AsyncSocketListener).FullName, + ex.SocketErrorCode.ToString("G"), + ex); + } + } + + private class SocketStateObject + { + public Socket Socket { get; private set; } + + public readonly byte[] Buffer = new byte[1024]; + + public SocketStateObject(Socket handler) + { + Socket = handler; + } + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/Common/RemoteSshdConfigExtensions.cs b/src/Renci.SshNet.IntegrationTests/Common/RemoteSshdConfigExtensions.cs new file mode 100644 index 000000000..64676a0d2 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/Common/RemoteSshdConfigExtensions.cs @@ -0,0 +1,31 @@ +using Renci.SshNet.TestTools.OpenSSH; + +namespace Renci.SshNet.IntegrationTests.Common +{ + internal static class RemoteSshdConfigExtensions + { + private const string DefaultAuthenticationMethods = "password publickey"; + + public static void Reset(this RemoteSshdConfig remoteSshdConfig) + { + remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, DefaultAuthenticationMethods) + .WithChallengeResponseAuthentication(false) + .WithKeyboardInteractiveAuthentication(false) + .PrintMotd() + .WithLogLevel(LogLevel.Debug3) + .ClearHostKeyFiles() + .AddHostKeyFile(HostKeyFile.Rsa.FilePath) + .ClearSubsystems() + .AddSubsystem(new Subsystem("sftp", "/usr/lib/ssh/sftp-server")) + .ClearCiphers() + .ClearKeyExchangeAlgorithms() + .ClearHostKeyAlgorithms() + .AddHostKeyAlgorithm(HostKeyAlgorithm.SshRsa) + .ClearPublicKeyAcceptedAlgorithms() + .AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.SshRsa) + .WithUsePAM(true) + .Update() + .Restart(); + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/Common/Socks5Handler.cs b/src/Renci.SshNet.IntegrationTests/Common/Socks5Handler.cs new file mode 100644 index 000000000..e50858c33 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/Common/Socks5Handler.cs @@ -0,0 +1,255 @@ +using System.Net; +using System.Net.Sockets; + +using Renci.SshNet.Abstractions; +using Renci.SshNet.Common; +using Renci.SshNet.Messages.Transport; + +namespace Renci.SshNet.IntegrationTests.Common +{ + class Socks5Handler + { + private readonly IPEndPoint _proxyEndPoint; + private readonly string _userName; + private readonly string _password; + + public Socks5Handler(IPEndPoint proxyEndPoint, string userName, string password) + { + _proxyEndPoint = proxyEndPoint; + _userName = userName; + _password = password; + } + + public Socket Connect(IPEndPoint endPoint) + { + if (endPoint == null) + { + throw new ArgumentNullException("endPoint"); + } + + var addressBytes = GetAddressBytes(endPoint); + return Connect(addressBytes, endPoint.Port); + } + + public Socket Connect(string host, int port) + { + if (host == null) + { + throw new ArgumentNullException(nameof(host)); + } + + if (host.Length > byte.MaxValue) + { + throw new ArgumentException($@"Cannot be more than {byte.MaxValue} characters.", nameof(host)); + } + + var addressBytes = new byte[host.Length + 2]; + addressBytes[0] = 0x03; + addressBytes[1] = (byte) host.Length; + Encoding.ASCII.GetBytes(host, 0, host.Length, addressBytes, 2); + return Connect(addressBytes, port); + } + + private Socket Connect(byte[] addressBytes, int port) + { + var socket = SocketAbstraction.Connect(_proxyEndPoint, TimeSpan.FromSeconds(5)); + + // Send socks version number + SocketWriteByte(socket, 0x05); + + // Send number of supported authentication methods + SocketWriteByte(socket, 0x02); + + // Send supported authentication methods + SocketWriteByte(socket, 0x00); // No authentication + SocketWriteByte(socket, 0x02); // Username/Password + + var socksVersion = SocketReadByte(socket); + if (socksVersion != 0x05) + { + throw new ProxyException(string.Format("SOCKS Version '{0}' is not supported.", socksVersion)); + } + + var authenticationMethod = SocketReadByte(socket); + switch (authenticationMethod) + { + case 0x00: + break; + case 0x02: + + // Send version + SocketWriteByte(socket, 0x01); + + var username = Encoding.ASCII.GetBytes(_userName); + if (username.Length > byte.MaxValue) + { + throw new ProxyException("Proxy username is too long."); + } + + // Send username length + SocketWriteByte(socket, (byte) username.Length); + + // Send username + SocketAbstraction.Send(socket, username); + + var password = Encoding.ASCII.GetBytes(_password); + + if (password.Length > byte.MaxValue) + { + throw new ProxyException("Proxy password is too long."); + } + + // Send username length + SocketWriteByte(socket, (byte) password.Length); + + // Send username + SocketAbstraction.Send(socket, password); + + var serverVersion = SocketReadByte(socket); + + if (serverVersion != 1) + { + throw new ProxyException("SOCKS5: Server authentication version is not valid."); + } + + var statusCode = SocketReadByte(socket); + if (statusCode != 0) + { + throw new ProxyException("SOCKS5: Username/Password authentication failed."); + } + + break; + case 0xFF: + throw new ProxyException("SOCKS5: No acceptable authentication methods were offered."); + default: + throw new ProxyException("SOCKS5: No acceptable authentication methods were offered."); + } + + // Send socks version number + SocketWriteByte(socket, 0x05); + + // Send command code + SocketWriteByte(socket, 0x01); // establish a TCP/IP stream connection + + // Send reserved, must be 0x00 + SocketWriteByte(socket, 0x00); + + // Send address type and address + SocketAbstraction.Send(socket, addressBytes); + + // Send port + SocketWriteByte(socket, (byte)(port / 0xFF)); + SocketWriteByte(socket, (byte)(port % 0xFF)); + + // Read Server SOCKS5 version + if (SocketReadByte(socket) != 5) + { + throw new ProxyException("SOCKS5: Version 5 is expected."); + } + + // Read response code + var status = SocketReadByte(socket); + + switch (status) + { + case 0x00: + break; + case 0x01: + throw new ProxyException("SOCKS5: General failure."); + case 0x02: + throw new ProxyException("SOCKS5: Connection not allowed by ruleset."); + case 0x03: + throw new ProxyException("SOCKS5: Network unreachable."); + case 0x04: + throw new ProxyException("SOCKS5: Host unreachable."); + case 0x05: + throw new ProxyException("SOCKS5: Connection refused by destination host."); + case 0x06: + throw new ProxyException("SOCKS5: TTL expired."); + case 0x07: + throw new ProxyException("SOCKS5: Command not supported or protocol error."); + case 0x08: + throw new ProxyException("SOCKS5: Address type not supported."); + default: + throw new ProxyException("SOCKS4: Not valid response."); + } + + // Read 0 + if (SocketReadByte(socket) != 0) + { + throw new ProxyException("SOCKS5: 0 byte is expected."); + } + + var addressType = SocketReadByte(socket); + var responseIp = new byte[16]; + + switch (addressType) + { + case 0x01: + SocketRead(socket, responseIp, 0, 4); + break; + case 0x04: + SocketRead(socket, responseIp, 0, 16); + break; + default: + throw new ProxyException(string.Format("Address type '{0}' is not supported.", addressType)); + } + + var portBytes = new byte[2]; + + // Read 2 bytes to be ignored + SocketRead(socket, portBytes, 0, 2); + + return socket; + } + + private static byte[] GetAddressBytes(IPEndPoint endPoint) + { + if (endPoint.AddressFamily == AddressFamily.InterNetwork) + { + var addressBytes = new byte[4 + 1]; + addressBytes[0] = 0x01; + var address = endPoint.Address.GetAddressBytes(); + Buffer.BlockCopy(address, 0, addressBytes, 1, address.Length); + return addressBytes; + } + + if (endPoint.AddressFamily == AddressFamily.InterNetworkV6) + { + var addressBytes = new byte[16 + 1]; + addressBytes[0] = 0x04; + var address = endPoint.Address.GetAddressBytes(); + Buffer.BlockCopy(address, 0, addressBytes, 1, address.Length); + return addressBytes; + } + + throw new ProxyException(string.Format("SOCKS5: IP address '{0}' is not supported.", endPoint.Address)); + } + + private static void SocketWriteByte(Socket socket, byte data) + { + SocketAbstraction.Send(socket, new[] { data }); + } + + private static byte SocketReadByte(Socket socket) + { + var buffer = new byte[1]; + SocketRead(socket, buffer, 0, 1); + return buffer[0]; + } + + private static int SocketRead(Socket socket, byte[] buffer, int offset, int length) + { + var bytesRead = SocketAbstraction.Read(socket, buffer, offset, length, TimeSpan.FromMilliseconds(-1)); + if (bytesRead == 0) + { + // when we're in the disconnecting state (either triggered by client or server), then the + // SshConnectionException will interrupt the message listener loop (if not already interrupted) + // and the exception itself will be ignored (in RaiseError) + throw new SshConnectionException("An established connection was aborted by the server.", + DisconnectReason.ConnectionLost); + } + return bytesRead; + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/ConnectivityTests.cs b/src/Renci.SshNet.IntegrationTests/ConnectivityTests.cs new file mode 100644 index 000000000..5401e6b6b --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/ConnectivityTests.cs @@ -0,0 +1,415 @@ +using Renci.SshNet.Common; +using Renci.SshNet.IntegrationTests.Common; +using Renci.SshNet.Messages.Transport; + +namespace Renci.SshNet.IntegrationTests +{ + [TestClass] + public class ConnectivityTests : IntegrationTestBase + { + private AuthenticationMethodFactory _authenticationMethodFactory; + private IConnectionInfoFactory _connectionInfoFactory; + private IConnectionInfoFactory _adminConnectionInfoFactory; + private RemoteSshdConfig _remoteSshdConfig; + private SshConnectionDisruptor _sshConnectionDisruptor; + + [TestInitialize] + public void SetUp() + { + _authenticationMethodFactory = new AuthenticationMethodFactory(); + _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort, _authenticationMethodFactory); + _adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort); + _remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig(); + _sshConnectionDisruptor = new SshConnectionDisruptor(_adminConnectionInfoFactory); + } + + [TestCleanup] + public void TearDown() + { + _remoteSshdConfig?.Reset(); + } + + [TestMethod] + public void Common_CreateMoreChannelsThanMaxSessions() + { + var connectionInfo = _connectionInfoFactory.Create(); + connectionInfo.MaxSessions = 2; + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + + // create one more channel than the maximum number of sessions + // as that would block indefinitely when creating the last channel + // if the channel would not be properly closed + for (var i = 0; i < connectionInfo.MaxSessions + 1; i++) + { + using (var stream = client.CreateShellStream("vt220", 20, 20, 20, 20, 0)) + { + stream.WriteLine("echo test"); + stream.ReadLine(); + } + } + } + } + + [TestMethod] + public void Common_DisposeAfterLossOfNetworkConnectivity() + { + var hostNetworkConnectionDisabled = false; + SshConnectionRestorer disruptor = null; + try + { + Exception errorOccurred = null; + int count = 0; + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.ErrorOccurred += (sender, args) => + { + Console.WriteLine("Exception " + count++); + Console.WriteLine(args.Exception); + errorOccurred = args.Exception; + }; + client.Connect(); + + disruptor = _sshConnectionDisruptor.BreakConnections(); + hostNetworkConnectionDisabled = true; + WaitForConnectionInterruption(client); + } + + Assert.IsNotNull(errorOccurred); + Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType()); + + var connectionException = (SshConnectionException) errorOccurred; + Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason); + Assert.IsNull(connectionException.InnerException); + Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message); + } + finally + { + if (hostNetworkConnectionDisabled) + { + disruptor?.RestoreConnections(); + disruptor?.Dispose(); + } + } + } + + [TestMethod] + public void Common_DetectLossOfNetworkConnectivityThroughKeepAlive() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + Exception errorOccurred = null; + int count = 0; + client.ErrorOccurred += (sender, args) => + { + Console.WriteLine("Exception "+ count++); + Console.WriteLine(args.Exception); + errorOccurred = args.Exception; + }; + client.KeepAliveInterval = new TimeSpan(0, 0, 0, 0, 50); + client.Connect(); + + var disruptor = _sshConnectionDisruptor.BreakConnections(); + + try + { + WaitForConnectionInterruption(client); + + Assert.IsFalse(client.IsConnected); + + Assert.IsNotNull(errorOccurred); + Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType()); + + var connectionException = (SshConnectionException) errorOccurred; + Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason); + Assert.IsNull(connectionException.InnerException); + Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message); + } + finally + { + disruptor?.RestoreConnections(); + disruptor?.Dispose(); + } + } + } + + private static void WaitForConnectionInterruption(SftpClient client) + { + for (var i = 0; i < 500; i++) + { + if (!client.IsConnected) + { + break; + } + + Thread.Sleep(100); + } + + // After interruption, you have to wait for the events to propagate. + Thread.Sleep(100); + } + + [TestMethod] + public void Common_DetectConnectionResetThroughSftpInvocation() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.KeepAliveInterval = TimeSpan.FromSeconds(1); + client.OperationTimeout = TimeSpan.FromSeconds(60); + ManualResetEvent errorOccurredSignaled = new ManualResetEvent(false); + Exception errorOccurred = null; + client.ErrorOccurred += (sender, args) => + { + errorOccurred = args.Exception; + errorOccurredSignaled.Set(); + }; + client.Connect(); + + var disruptor = _sshConnectionDisruptor.BreakConnections(); + + try + { + client.ListDirectory("/"); + Assert.Fail(); + } + catch (SshConnectionException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("Client not connected.", ex.Message); + + Assert.IsNotNull(errorOccurred); + Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType()); + + var connectionException = (SshConnectionException) errorOccurred; + Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason); + Assert.IsNull(connectionException.InnerException); + Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message); + } + finally + { + disruptor.RestoreConnections(); + disruptor.Dispose(); + } + } + } + + [TestMethod] + public void Common_LossOfNetworkConnectivityDisconnectAndConnect() + { + bool vmNetworkConnectionDisabled = false; + SshConnectionRestorer disruptor = null; + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + Exception errorOccurred = null; + client.ErrorOccurred += (sender, args) => errorOccurred = args.Exception; + + client.Connect(); + + disruptor = _sshConnectionDisruptor.BreakConnections(); + vmNetworkConnectionDisabled = true; + + WaitForConnectionInterruption(client); + // disconnect while network connectivity is lost + client.Disconnect(); + + Assert.IsFalse(client.IsConnected); + + disruptor.RestoreConnections(); + vmNetworkConnectionDisabled = false; + + // connect when network connectivity is restored + client.Connect(); + client.ChangeDirectory(client.WorkingDirectory); + client.Dispose(); + + Assert.IsNotNull(errorOccurred); + Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType()); + + var connectionException = (SshConnectionException) errorOccurred; + Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason); + Assert.IsNull(connectionException.InnerException); + Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message); + } + } + finally + { + if (vmNetworkConnectionDisabled) + { + disruptor.RestoreConnections(); + } + disruptor?.Dispose(); + } + } + + [TestMethod] + public void Common_DetectLossOfNetworkConnectivityThroughSftpInvocation() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + ManualResetEvent errorOccurredSignaled = new ManualResetEvent(false); + Exception errorOccurred = null; + client.ErrorOccurred += (sender, args) => + { + errorOccurred = args.Exception; + errorOccurredSignaled.Set(); + }; + client.Connect(); + + var disruptor = _sshConnectionDisruptor.BreakConnections(); + Thread.Sleep(100); + try + { + client.ListDirectory("/"); + Assert.Fail(); + } + catch (SshConnectionException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("Client not connected.", ex.Message); + + Assert.IsNotNull(errorOccurred); + Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType()); + + var connectionException = (SshConnectionException) errorOccurred; + Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason); + Assert.IsNull(connectionException.InnerException); + Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message); + } + finally + { + disruptor.RestoreConnections(); + disruptor.Dispose(); + } + } + } + + [TestMethod] + public void Common_DetectSessionKilledOnServer() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + ManualResetEvent errorOccurredSignaled = new ManualResetEvent(false); + Exception errorOccurred = null; + client.ErrorOccurred += (sender, args) => + { + errorOccurred = args.Exception; + errorOccurredSignaled.Set(); + }; + client.Connect(); + + // Kill the server session + using (var adminClient = new SshClient(_adminConnectionInfoFactory.Create())) + { + adminClient.Connect(); + + var command = $"sudo ps --no-headers -u {client.ConnectionInfo.Username} -f | grep \"{client.ConnectionInfo.Username}@notty\" | awk '{{print $2}}' | xargs sudo kill -9"; + var sshCommand = adminClient.CreateCommand(command); + var result = sshCommand.Execute(); + Assert.AreEqual(0, sshCommand.ExitStatus, sshCommand.Error); + } + + Assert.IsTrue(errorOccurredSignaled.WaitOne(200)); + Assert.IsNotNull(errorOccurred); + Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType()); + Assert.IsNull(errorOccurred.InnerException); + Assert.AreEqual("An established connection was aborted by the server.", errorOccurred.Message); + Assert.IsFalse(client.IsConnected); + } + } + + [TestMethod] + public void Common_HostKeyValidation_Failure() + { + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.HostKeyReceived += (sender, e) => { e.CanTrust = false; }; + + try + { + client.Connect(); + Assert.Fail(); + } + catch (SshConnectionException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("Key exchange negotiation failed.", ex.Message); + } + } + } + + [TestMethod] + public void Common_HostKeyValidation_Success() + { + byte[] host_rsa_key_openssh_fingerprint = + { + 0x3d, 0x90, 0xd8, 0x0d, 0xd5, 0xe0, 0xb6, 0x13, + 0x42, 0x7c, 0x78, 0x1e, 0x19, 0xa3, 0x99, 0x2b + }; + + var hostValidationSuccessful = false; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.HostKeyReceived += (sender, e) => + { + if (host_rsa_key_openssh_fingerprint.Length == e.FingerPrint.Length) + { + for (var i = 0; i < host_rsa_key_openssh_fingerprint.Length; i++) + { + if (host_rsa_key_openssh_fingerprint[i] != e.FingerPrint[i]) + { + e.CanTrust = false; + break; + } + } + + hostValidationSuccessful = e.CanTrust; + } + else + { + e.CanTrust = false; + } + }; + client.Connect(); + } + + Assert.IsTrue(hostValidationSuccessful); + } + + /// + /// Verifies whether we handle a disconnect initiated by the SSH server (through a SSH_MSG_DISCONNECT message). + /// + /// + /// We force this by only configuring keyboard-interactive as authentication method, while ChallengeResponseAuthentication + /// is not enabled. This causes OpenSSH to terminate the connection because there are no authentication methods left. + /// + [TestMethod] + public void Common_ServerRejectsConnection() + { + _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive") + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserKeyboardInteractiveAuthenticationMethod()); + using (var client = new SftpClient(connectionInfo)) + { + try + { + client.Connect(); + Assert.Fail(); + } + catch (SshConnectionException ex) + { + Assert.AreEqual(DisconnectReason.ProtocolError, ex.DisconnectReason); + Assert.IsNull(ex.InnerException); + Assert.AreEqual("The connection was closed by the server: no authentication methods enabled (ProtocolError).", ex.Message); + } + } + } + + } +} diff --git a/src/Renci.SshNet.IntegrationTests/Credential.cs b/src/Renci.SshNet.IntegrationTests/Credential.cs new file mode 100644 index 000000000..ee62d0f7b --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/Credential.cs @@ -0,0 +1,14 @@ +namespace Renci.SshNet.IntegrationTests +{ + internal class Credential + { + public Credential(string userName, string password) + { + UserName = userName; + Password = password; + } + + public string UserName { get; } + public string Password { get; } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/HostConfig.cs b/src/Renci.SshNet.IntegrationTests/HostConfig.cs new file mode 100644 index 000000000..817bd7026 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/HostConfig.cs @@ -0,0 +1,115 @@ +using System.Net; +using System.Text.RegularExpressions; + +namespace Renci.SshNet.IntegrationTests +{ + class HostConfig + { + private static readonly Regex HostsEntryRegEx = new Regex(@"^(?[\S]+)\s+(?[a-zA-Z]+[a-zA-Z\-\.]*[a-zA-Z]+)\s*(?.+)*$", RegexOptions.Singleline); + + public List Entries { get; } + + private HostConfig() + { + Entries = new List(); + } + + public static HostConfig Read(ScpClient scpClient, string path) + { + HostConfig hostConfig = new HostConfig(); + + using (var ms = new MemoryStream()) + { + scpClient.Download(path, ms); + ms.Position = 0; + + using (var sr = new StreamReader(ms, Encoding.ASCII)) + { + string line; + while ((line = sr.ReadLine()) != null) + { + // skip comments + if (line.StartsWith("#")) + { + continue; + } + + var hostEntryMatch = HostsEntryRegEx.Match(line); + if (!hostEntryMatch.Success) + { + continue; + } + + var entryIPAddress = hostEntryMatch.Groups["IPAddress"].Value; + var entryAliasesGroup = hostEntryMatch.Groups["Aliases"]; + + var entry = new HostEntry(IPAddress.Parse(entryIPAddress), hostEntryMatch.Groups["HostName"].Value); + + if (entryAliasesGroup.Success) + { + var aliases = entryAliasesGroup.Value.Split(' '); + foreach (var alias in aliases) + { + entry.Aliases.Add(alias); + } + } + + hostConfig.Entries.Add(entry); + } + } + } + + return hostConfig; + } + + public void Write(ScpClient scpClient, string path) + { + using (var ms = new MemoryStream()) + using (var sw = new StreamWriter(ms, Encoding.ASCII)) + { + // Use linux line ending + sw.NewLine = "\n"; + + foreach (var hostEntry in Entries) + { + sw.Write(hostEntry.IPAddress); + sw.Write(" "); + sw.Write(hostEntry.HostName); + + if (hostEntry.Aliases.Count > 0) + { + sw.Write(" "); + for (var i = 0; i < hostEntry.Aliases.Count; i++) + { + if (i > 0) + { + sw.Write(' '); + } + sw.Write(hostEntry.Aliases[i]); + } + } + sw.WriteLine(); + } + + sw.Flush(); + ms.Position = 0; + + scpClient.Upload(ms, path); + } + } + } + + public class HostEntry + { + public HostEntry(IPAddress ipAddress, string hostName) + { + IPAddress = ipAddress; + HostName = hostName; + Aliases = new List(); + } + + public IPAddress IPAddress { get; private set; } + public string HostName { get; set; } + public List Aliases { get; } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/HostKeyAlgorithmTests.cs b/src/Renci.SshNet.IntegrationTests/HostKeyAlgorithmTests.cs new file mode 100644 index 000000000..3732f1e22 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/HostKeyAlgorithmTests.cs @@ -0,0 +1,105 @@ +using Renci.SshNet.Common; +using Renci.SshNet.IntegrationTests.Common; +using Renci.SshNet.TestTools.OpenSSH; + +namespace Renci.SshNet.IntegrationTests +{ + [TestClass] + public class HostKeyAlgorithmTests : IntegrationTestBase + { + private IConnectionInfoFactory _connectionInfoFactory; + private RemoteSshdConfig _remoteSshdConfig; + + [TestInitialize] + public void SetUp() + { + _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort); + _remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig(); + } + + [TestCleanup] + public void TearDown() + { + _remoteSshdConfig?.Reset(); + } + + [TestMethod] + [Ignore] // No longer supported in recent versions of OpenSSH + public void SshDsa() + { + _remoteSshdConfig.ClearHostKeyAlgorithms() + .AddHostKeyAlgorithm(HostKeyAlgorithm.SshDsa) + .ClearHostKeyFiles() + .AddHostKeyFile(HostKeyFile.Dsa.FilePath) + .Update() + .Restart(); + + HostKeyEventArgs hostKeyEventsArgs = null; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.HostKeyReceived += (sender, e) => hostKeyEventsArgs = e; + client.Connect(); + client.Disconnect(); + } + + Assert.IsNotNull(hostKeyEventsArgs); + Assert.AreEqual(HostKeyFile.Dsa.KeyName, hostKeyEventsArgs.HostKeyName); + Assert.AreEqual(1024, hostKeyEventsArgs.KeyLength); + Assert.IsTrue(hostKeyEventsArgs.FingerPrint.SequenceEqual(HostKeyFile.Dsa.FingerPrint)); + } + + [TestMethod] + public void SshRsa() + { + _remoteSshdConfig.ClearHostKeyAlgorithms() + .AddHostKeyAlgorithm(HostKeyAlgorithm.SshRsa) + .Update() + .Restart(); + + HostKeyEventArgs hostKeyEventsArgs = null; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.HostKeyReceived += (sender, e) => hostKeyEventsArgs = e; + client.Connect(); + client.Disconnect(); + } + + Assert.IsNotNull(hostKeyEventsArgs); + Assert.AreEqual(HostKeyFile.Rsa.KeyName, hostKeyEventsArgs.HostKeyName); + Assert.AreEqual(3072, hostKeyEventsArgs.KeyLength); + Assert.IsTrue(hostKeyEventsArgs.FingerPrint.SequenceEqual(HostKeyFile.Rsa.FingerPrint)); + } + + [TestMethod] + public void SshEd25519() + { + _remoteSshdConfig.ClearHostKeyAlgorithms() + .AddHostKeyAlgorithm(HostKeyAlgorithm.SshEd25519) + .ClearHostKeyFiles() + .AddHostKeyFile(HostKeyFile.Ed25519.FilePath) + .Update() + .Restart(); + + HostKeyEventArgs hostKeyEventsArgs = null; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.HostKeyReceived += (sender, e) => hostKeyEventsArgs = e; + client.Connect(); + client.Disconnect(); + } + + Assert.IsNotNull(hostKeyEventsArgs); + Assert.AreEqual(HostKeyFile.Ed25519.KeyName, hostKeyEventsArgs.HostKeyName); + Assert.AreEqual(256, hostKeyEventsArgs.KeyLength); + Assert.IsTrue(hostKeyEventsArgs.FingerPrint.SequenceEqual(HostKeyFile.Ed25519.FingerPrint)); + } + + private void Client_HostKeyReceived(object sender, HostKeyEventArgs e) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/HostKeyFile.cs b/src/Renci.SshNet.IntegrationTests/HostKeyFile.cs new file mode 100644 index 000000000..01cf957f5 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/HostKeyFile.cs @@ -0,0 +1,23 @@ +namespace Renci.SshNet.IntegrationTests +{ + public sealed class HostKeyFile + { + public static readonly HostKeyFile Rsa = new HostKeyFile("ssh-rsa", "/etc/ssh/ssh_host_rsa_key", new byte[] { 0x3d, 0x90, 0xd8, 0x0d, 0xd5, 0xe0, 0xb6, 0x13, 0x42, 0x7c, 0x78, 0x1e, 0x19, 0xa3, 0x99, 0x2b }); + public static readonly HostKeyFile Dsa = new HostKeyFile("ssh-dsa", "/etc/ssh/ssh_host_dsa_key", new byte[] { 0x3d, 0x90, 0xd8, 0x0d, 0xd5, 0xe0, 0xb6, 0x13, 0x42, 0x7c, 0x78, 0x1e, 0x19, 0xa3, 0x99, 0x2b }); + public static readonly HostKeyFile Ed25519 = new HostKeyFile("ssh-ed25519", "/etc/ssh/ssh_host_ed25519_key", new byte[] { 0xb3, 0xb9, 0xd0, 0x1b, 0x73, 0xc4, 0x60, 0xb4, 0xce, 0xed, 0x06, 0xf8, 0x58, 0x49, 0xa3, 0xda }); + public const string Ecdsa = "/etc/ssh/ssh_host_ecdsa_key"; + + private HostKeyFile(string keyName, string filePath, byte[] fingerPrint) + { + KeyName = keyName; + FilePath = filePath; + FingerPrint = fingerPrint; + } + + public string KeyName {get; } + public string FilePath { get; } + public byte[] FingerPrint { get; } + } + + +} diff --git a/src/Renci.SshNet.IntegrationTests/IConnectionInfoFactory.cs b/src/Renci.SshNet.IntegrationTests/IConnectionInfoFactory.cs new file mode 100644 index 000000000..858dd0872 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/IConnectionInfoFactory.cs @@ -0,0 +1,10 @@ +namespace Renci.SshNet.IntegrationTests +{ + public interface IConnectionInfoFactory + { + ConnectionInfo Create(); + ConnectionInfo Create(params AuthenticationMethod[] authenticationMethods); + ConnectionInfo CreateWithProxy(); + ConnectionInfo CreateWithProxy(params AuthenticationMethod[] authenticationMethods); + } +} diff --git a/src/Renci.SshNet.IntegrationTests/KeyExchangeAlgorithmTests.cs b/src/Renci.SshNet.IntegrationTests/KeyExchangeAlgorithmTests.cs new file mode 100644 index 000000000..dfd6b6394 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/KeyExchangeAlgorithmTests.cs @@ -0,0 +1,206 @@ +using Renci.SshNet.IntegrationTests.Common; +using Renci.SshNet.TestTools.OpenSSH; + +namespace Renci.SshNet.IntegrationTests +{ + [TestClass] + public class KeyExchangeAlgorithmTests : IntegrationTestBase + { + private IConnectionInfoFactory _connectionInfoFactory; + private RemoteSshdConfig _remoteSshdConfig; + + [TestInitialize] + public void SetUp() + { + _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort); + _remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig(); + } + + [TestCleanup] + public void TearDown() + { + _remoteSshdConfig?.Reset(); + } + + [TestMethod] + public void Curve25519Sha256() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.Curve25519Sha256) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void Curve25519Sha256Libssh() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.Curve25519Sha256Libssh) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void DiffieHellmanGroup1Sha1() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup1Sha1) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void DiffieHellmanGroup14Sha1() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup14Sha1) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void DiffieHellmanGroup14Sha256() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup14Sha256) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void DiffieHellmanGroup16Sha512() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup16Sha512) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + [Ignore] + public void DiffieHellmanGroup18Sha512() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup18Sha512) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void DiffieHellmanGroupExchangeSha1() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroupExchangeSha1) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void DiffieHellmanGroupExchangeSha256() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroupExchangeSha256) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void EcdhSha2Nistp256() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.EcdhSha2Nistp256) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void EcdhSha2Nistp384() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.EcdhSha2Nistp384) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void EcdhSha2Nistp521() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.EcdhSha2Nistp521) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/LinuxAdminConnectionFactory.cs b/src/Renci.SshNet.IntegrationTests/LinuxAdminConnectionFactory.cs new file mode 100644 index 000000000..dfb5c1369 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/LinuxAdminConnectionFactory.cs @@ -0,0 +1,36 @@ +namespace Renci.SshNet.IntegrationTests +{ + public class LinuxAdminConnectionFactory : IConnectionInfoFactory + { + private readonly string _host; + private readonly int _port; + + public LinuxAdminConnectionFactory(string sshServerHostName, ushort sshServerPort) + { + _host = sshServerHostName; + _port = sshServerPort; + } + + public ConnectionInfo Create() + { + var user = Users.Admin; + return new ConnectionInfo(_host, _port, user.UserName, new PasswordAuthenticationMethod(user.UserName, user.Password)); + } + + public ConnectionInfo Create(params AuthenticationMethod[] authenticationMethods) + { + throw new NotImplementedException(); + } + + public ConnectionInfo CreateWithProxy() + { + throw new NotImplementedException(); + } + + public ConnectionInfo CreateWithProxy(params AuthenticationMethod[] authenticationMethods) + { + throw new NotImplementedException(); + } + } +} + diff --git a/src/Renci.SshNet.IntegrationTests/LinuxVMConnectionFactory.cs b/src/Renci.SshNet.IntegrationTests/LinuxVMConnectionFactory.cs new file mode 100644 index 000000000..f2f04dbfc --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/LinuxVMConnectionFactory.cs @@ -0,0 +1,62 @@ +namespace Renci.SshNet.IntegrationTests +{ + public class LinuxVMConnectionFactory : IConnectionInfoFactory + { + + + private const string ProxyHost = "127.0.0.1"; + private const int ProxyPort = 1234; + private const string ProxyUserName = "test"; + private const string ProxyPassword = "123"; + private readonly string _host; + private readonly int _port; + private readonly AuthenticationMethodFactory _authenticationMethodFactory; + + + public LinuxVMConnectionFactory(string sshServerHostName, ushort sshServerPort) + { + _host = sshServerHostName; + _port = sshServerPort; + + _authenticationMethodFactory = new AuthenticationMethodFactory(); + } + + public LinuxVMConnectionFactory(string sshServerHostName, ushort sshServerPort, AuthenticationMethodFactory authenticationMethodFactory) + { + _host = sshServerHostName; + _port = sshServerPort; + + _authenticationMethodFactory = authenticationMethodFactory; + } + + public ConnectionInfo Create() + { + return Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod()); + } + + public ConnectionInfo Create(params AuthenticationMethod[] authenticationMethods) + { + return new ConnectionInfo(_host, _port, Users.Regular.UserName, authenticationMethods); + } + + public ConnectionInfo CreateWithProxy() + { + return CreateWithProxy(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod()); + } + + public ConnectionInfo CreateWithProxy(params AuthenticationMethod[] authenticationMethods) + { + return new ConnectionInfo( + _host, + _port, + Users.Regular.UserName, + ProxyTypes.Socks4, + ProxyHost, + ProxyPort, + ProxyUserName, + ProxyPassword, + authenticationMethods); + } + } +} + diff --git a/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/AesCipherTests.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/AesCipherTests.cs new file mode 100644 index 000000000..7e9e97b01 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/AesCipherTests.cs @@ -0,0 +1,147 @@ +using Renci.SshNet.IntegrationTests.Common; +using Renci.SshNet.Security.Cryptography.Ciphers; +using Renci.SshNet.Security.Cryptography.Ciphers.Modes; +using Renci.SshNet.TestTools.OpenSSH; + +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests +{ + [TestClass] + public class AesCipherTests : IntegrationTestBase + { + private IConnectionInfoFactory _adminConnectionInfoFactory; + private RemoteSshdConfig _remoteSshdConfig; + + [TestInitialize] + public void SetUp() + { + _adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort); + _remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig(); + } + + [TestCleanup] + public void TearDown() + { + _remoteSshdConfig?.Reset(); + } + + [TestMethod] + [Owner("olegkap")] + [TestCategory("Cipher")] + public void Test_Cipher_AEes128CBC_Connection() + { + _remoteSshdConfig.AddCipher(Cipher.Aes128Cbc) + .Update() + .Restart(); + + var connectionInfo = new PasswordConnectionInfo(SshServerHostName, SshServerPort, User.UserName, User.Password); + connectionInfo.Encryptions.Clear(); + connectionInfo.Encryptions.Add("aes128-cbc", new CipherInfo(128, (key, iv) => { return new AesCipher(key, new CbcCipherMode(iv), null); })); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + [Owner("olegkap")] + [TestCategory("Cipher")] + public void Test_Cipher_Aes192CBC_Connection() + { + _remoteSshdConfig.AddCipher(Cipher.Aes192Cbc) + .Update() + .Restart(); + + var connectionInfo = new PasswordConnectionInfo(SshServerHostName, SshServerPort, User.UserName, User.Password); + connectionInfo.Encryptions.Clear(); + connectionInfo.Encryptions.Add("aes192-cbc", new CipherInfo(192, (key, iv) => { return new AesCipher(key, new CbcCipherMode(iv), null); })); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + [Owner("olegkap")] + [TestCategory("Cipher")] + public void Test_Cipher_Aes256CBC_Connection() + { + _remoteSshdConfig.AddCipher(Cipher.Aes256Cbc) + .Update() + .Restart(); + + var connectionInfo = new PasswordConnectionInfo(SshServerHostName, SshServerPort, User.UserName, User.Password); + connectionInfo.Encryptions.Clear(); + connectionInfo.Encryptions.Add("aes256-cbc", new CipherInfo(256, (key, iv) => { return new AesCipher(key, new CbcCipherMode(iv), null); })); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + [Owner("olegkap")] + [TestCategory("Cipher")] + public void Test_Cipher_Aes128CTR_Connection() + { + _remoteSshdConfig.AddCipher(Cipher.Aes128Ctr) + .Update() + .Restart(); + + var connectionInfo = new PasswordConnectionInfo(SshServerHostName, SshServerPort, User.UserName, User.Password); + connectionInfo.Encryptions.Clear(); + connectionInfo.Encryptions.Add("aes128-ctr", new CipherInfo(128, (key, iv) => { return new AesCipher(key, new CtrCipherMode(iv), null); })); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + [Owner("olegkap")] + [TestCategory("Cipher")] + public void Test_Cipher_Aes192CTR_Connection() + { + _remoteSshdConfig.AddCipher(Cipher.Aes192Ctr) + .Update() + .Restart(); + + var connectionInfo = new PasswordConnectionInfo(SshServerHostName, SshServerPort, User.UserName, User.Password); + connectionInfo.Encryptions.Clear(); + connectionInfo.Encryptions.Add("aes192-ctr", new CipherInfo(192, (key, iv) => { return new AesCipher(key, new CtrCipherMode(iv), null); })); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + [Owner("olegkap")] + [TestCategory("Cipher")] + public void Test_Cipher_Aes256CTR_Connection() + { + _remoteSshdConfig.AddCipher(Cipher.Aes256Ctr) + .Update() + .Restart(); + + var connectionInfo = new PasswordConnectionInfo(SshServerHostName, SshServerPort, User.UserName, User.Password); + connectionInfo.Encryptions.Clear(); + connectionInfo.Encryptions.Add("aes256-ctr", new CipherInfo(256, (key, iv) => { return new AesCipher(key, new CtrCipherMode(iv), null); })); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + client.Disconnect(); + } + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/ForwardedPortLocalTest.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/ForwardedPortLocalTest.cs new file mode 100644 index 000000000..bf6292faa --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/ForwardedPortLocalTest.cs @@ -0,0 +1,158 @@ +using System.Diagnostics; + +using Renci.SshNet.Common; + +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests +{ + /// + /// Provides functionality for local port forwarding + /// + [TestClass] + public class ForwardedPortLocalTest : IntegrationTestBase + { + [TestMethod] + [WorkItem(713)] + [Owner("Kenneth_aa")] + [TestCategory("PortForwarding")] + [Description("Test if calling Stop on ForwardedPortLocal instance causes wait.")] + public void Test_PortForwarding_Local_Stop_Hangs_On_Wait() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + + var port1 = new ForwardedPortLocal("localhost", 8084, "www.google.com", 80); + client.AddForwardedPort(port1); + port1.Exception += delegate (object sender, ExceptionEventArgs e) + { + Assert.Fail(e.Exception.ToString()); + }; + + port1.Start(); + + var hasTestedTunnel = false; + + _ = ThreadPool.QueueUserWorkItem(delegate (object state) + { + try + { + var url = "http://www.google.com/"; + Debug.WriteLine("Starting web request to \"" + url + "\""); + +#if NET6_0_OR_GREATER + var httpClient = new HttpClient(); + var response = httpClient.GetAsync(url) + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(); +#else + var request = (HttpWebRequest) WebRequest.Create(url); + var response = (HttpWebResponse) request.GetResponse(); +#endif // NET6_0_OR_GREATER + + Assert.IsNotNull(response); + + Debug.WriteLine("Http Response status code: " + response.StatusCode.ToString()); + + response.Dispose(); + + hasTestedTunnel = true; + } + catch (Exception ex) + { + Assert.Fail(ex.ToString()); + } + }); + + // Wait for the web request to complete. + while (!hasTestedTunnel) + { + Thread.Sleep(1000); + } + + try + { + // Try stop the port forwarding, wait 3 seconds and fail if it is still started. + _ = ThreadPool.QueueUserWorkItem(delegate (object state) + { + Debug.WriteLine("Trying to stop port forward."); + port1.Stop(); + Debug.WriteLine("Port forwarding stopped."); + }); + + Thread.Sleep(3000); + if (port1.IsStarted) + { + Assert.Fail("Port forwarding not stopped."); + } + } + catch (Exception ex) + { + Assert.Fail(ex.ToString()); + } + client.RemoveForwardedPort(port1); + client.Disconnect(); + Debug.WriteLine("Success."); + } + } + + [TestMethod] + [ExpectedException(typeof(SshConnectionException))] + public void Test_PortForwarding_Local_Without_Connecting() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + var port1 = new ForwardedPortLocal("localhost", 8084, "www.renci.org", 80); + client.AddForwardedPort(port1); + port1.Exception += delegate (object sender, ExceptionEventArgs e) + { + Assert.Fail(e.Exception.ToString()); + }; + port1.Start(); + + var test = Parallel.For(0, + 100, + counter => + { + var start = DateTime.Now; + +#if NET6_0_OR_GREATER + var httpClient = new HttpClient(); + using (var response = httpClient.GetAsync("http://localhost:8084").GetAwaiter().GetResult()) + { + var data = ReadStream(response.Content.ReadAsStream()); +#else + var request = (HttpWebRequest) WebRequest.Create("http://localhost:8084"); + using (var response = (HttpWebResponse) request.GetResponse()) + { + var data = ReadStream(response.GetResponseStream()); +#endif // NET6_0_OR_GREATER + var end = DateTime.Now; + + Debug.WriteLine(string.Format("Request# {2}: Lenght: {0} Time: {1}", data.Length, end - start, counter)); + } + }); + } + } + + private static byte[] ReadStream(Stream stream) + { + var buffer = new byte[1024]; + using (var ms = new MemoryStream()) + { + while (true) + { + var read = stream.Read(buffer, 0, buffer.Length); + if (read > 0) + { + ms.Write(buffer, 0, read); + } + else + { + return ms.ToArray(); + } + } + } + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/HMacTest.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/HMacTest.cs new file mode 100644 index 000000000..c8df34e9d --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/HMacTest.cs @@ -0,0 +1,67 @@ +using Renci.SshNet.Abstractions; +using Renci.SshNet.IntegrationTests.Common; + +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests +{ + [TestClass] + public class HMacTest : IntegrationTestBase + { + private IConnectionInfoFactory _adminConnectionInfoFactory; + private RemoteSshdConfig _remoteSshdConfig; + + [TestInitialize] + public void SetUp() + { + _adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort); + _remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig(); + } + + [TestCleanup] + public void TearDown() + { + _remoteSshdConfig?.Reset(); + } + + [TestMethod] + public void Test_HMac_Sha1_Connection() + { + var connectionInfo = new PasswordConnectionInfo(SshServerHostName, SshServerPort, User.UserName, User.Password); + connectionInfo.HmacAlgorithms.Clear(); + connectionInfo.HmacAlgorithms.Add("hmac-sha1", new HashInfo(20 * 8, CryptoAbstraction.CreateHMACSHA1)); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void Test_HMac_Sha256_Connection() + { + var connectionInfo = new PasswordConnectionInfo(SshServerHostName, SshServerPort, User.UserName, User.Password); + connectionInfo.HmacAlgorithms.Clear(); + connectionInfo.HmacAlgorithms.Add("hmac-sha2-256", new HashInfo(32 * 8, CryptoAbstraction.CreateHMACSHA256)); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void Test_HMac_Sha2_512_Connection() + { + var connectionInfo = new PasswordConnectionInfo(SshServerHostName, SshServerPort, User.UserName, User.Password); + connectionInfo.HmacAlgorithms.Clear(); + connectionInfo.HmacAlgorithms.Add("hmac-sha2-512", new HashInfo(64 * 8, CryptoAbstraction.CreateHMACSHA512)); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + client.Disconnect(); + } + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/ScpClientTest.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/ScpClientTest.cs new file mode 100644 index 000000000..e9015a3c6 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/ScpClientTest.cs @@ -0,0 +1,336 @@ +using System.Security.Cryptography; + +using Renci.SshNet.Common; + +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests +{ + /// + /// Provides SCP client functionality. + /// + [TestClass] + public partial class ScpClientTest : IntegrationTestBase + { + [TestMethod] + [TestCategory("Scp")] + public void Test_Scp_File_Upload_Download() + { + RemoveAllFiles(); + + using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + scp.Connect(); + + var uploadedFileName = Path.GetTempFileName(); + var downloadedFileName = Path.GetTempFileName(); + + CreateTestFile(uploadedFileName, 1); + + scp.Upload(new FileInfo(uploadedFileName), Path.GetFileName(uploadedFileName)); + + scp.Download(Path.GetFileName(uploadedFileName), new FileInfo(downloadedFileName)); + + // Calculate MD5 value + var uploadedHash = CalculateMD5(uploadedFileName); + var downloadedHash = CalculateMD5(downloadedFileName); + + File.Delete(uploadedFileName); + File.Delete(downloadedFileName); + + scp.Disconnect(); + + Assert.AreEqual(uploadedHash, downloadedHash); + } + } + + [TestMethod] + [TestCategory("Scp")] + public void Test_Scp_Stream_Upload_Download() + { + RemoveAllFiles(); + + using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + scp.Connect(); + + var uploadedFileName = Path.GetTempFileName(); + var downloadedFileName = Path.GetTempFileName(); + + CreateTestFile(uploadedFileName, 1); + + // Calculate has value + using (var stream = File.OpenRead(uploadedFileName)) + { + scp.Upload(stream, Path.GetFileName(uploadedFileName)); + } + + using (var stream = File.OpenWrite(downloadedFileName)) + { + scp.Download(Path.GetFileName(uploadedFileName), stream); + } + + // Calculate MD5 value + var uploadedHash = CalculateMD5(uploadedFileName); + var downloadedHash = CalculateMD5(downloadedFileName); + + File.Delete(uploadedFileName); + File.Delete(downloadedFileName); + + scp.Disconnect(); + + Assert.AreEqual(uploadedHash, downloadedHash); + } + } + + [TestMethod] + [TestCategory("Scp")] + public void Test_Scp_10MB_File_Upload_Download() + { + RemoveAllFiles(); + + using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + scp.Connect(); + + var uploadedFileName = Path.GetTempFileName(); + var downloadedFileName = Path.GetTempFileName(); + + CreateTestFile(uploadedFileName, 10); + + scp.Upload(new FileInfo(uploadedFileName), Path.GetFileName(uploadedFileName)); + + scp.Download(Path.GetFileName(uploadedFileName), new FileInfo(downloadedFileName)); + + // Calculate MD5 value + var uploadedHash = CalculateMD5(uploadedFileName); + var downloadedHash = CalculateMD5(downloadedFileName); + + File.Delete(uploadedFileName); + File.Delete(downloadedFileName); + + scp.Disconnect(); + + Assert.AreEqual(uploadedHash, downloadedHash); + } + } + + [TestMethod] + [TestCategory("Scp")] + public void Test_Scp_10MB_Stream_Upload_Download() + { + RemoveAllFiles(); + + using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + scp.Connect(); + + var uploadedFileName = Path.GetTempFileName(); + var downloadedFileName = Path.GetTempFileName(); + + CreateTestFile(uploadedFileName, 10); + + // Calculate has value + using (var stream = File.OpenRead(uploadedFileName)) + { + scp.Upload(stream, Path.GetFileName(uploadedFileName)); + } + + using (var stream = File.OpenWrite(downloadedFileName)) + { + scp.Download(Path.GetFileName(uploadedFileName), stream); + } + + // Calculate MD5 value + var uploadedHash = CalculateMD5(uploadedFileName); + var downloadedHash = CalculateMD5(downloadedFileName); + + File.Delete(uploadedFileName); + File.Delete(downloadedFileName); + + scp.Disconnect(); + + Assert.AreEqual(uploadedHash, downloadedHash); + } + } + + [TestMethod] + [TestCategory("Scp")] + public void Test_Scp_Directory_Upload_Download() + { + RemoveAllFiles(); + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + sftp.CreateDirectory("uploaded_dir"); + } + + using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + scp.Connect(); + + var uploadDirectory = + Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName())); + for (var i = 0; i < 3; i++) + { + var subfolder = Directory.CreateDirectory(string.Format(@"{0}\folder_{1}", uploadDirectory.FullName, i)); + + for (var j = 0; j < 5; j++) + { + CreateTestFile(string.Format(@"{0}\file_{1}", subfolder.FullName, j), 1); + } + + CreateTestFile(string.Format(@"{0}\file_{1}", uploadDirectory.FullName, i), 1); + } + + scp.Upload(uploadDirectory, "uploaded_dir"); + + var downloadDirectory = + Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName())); + + scp.Download("uploaded_dir", downloadDirectory); + + var uploadedFiles = uploadDirectory.GetFiles("*.*", SearchOption.AllDirectories); + var downloadFiles = downloadDirectory.GetFiles("*.*", SearchOption.AllDirectories); + + var result = from f1 in uploadedFiles + from f2 in downloadFiles + where + f1.FullName.Substring(uploadDirectory.FullName.Length) == + f2.FullName.Substring(downloadDirectory.FullName.Length) + && CalculateMD5(f1.FullName) == CalculateMD5(f2.FullName) + select f1; + + var counter = result.Count(); + + scp.Disconnect(); + + Assert.IsTrue(counter == uploadedFiles.Length && uploadedFiles.Length == downloadFiles.Length); + } + RemoveAllFiles(); + } + + [TestMethod] + [TestCategory("Scp")] + public void Test_Scp_File_20_Parallel_Upload_Download() + { + using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + scp.Connect(); + + var uploadFilenames = new string[20]; + for (var i = 0; i < uploadFilenames.Length; i++) + { + uploadFilenames[i] = Path.GetTempFileName(); + CreateTestFile(uploadFilenames[i], 1); + } + + _ = Parallel.ForEach(uploadFilenames, + filename => + { + scp.Upload(new FileInfo(filename), Path.GetFileName(filename)); + }); + _ = Parallel.ForEach(uploadFilenames, + filename => + { + scp.Download(Path.GetFileName(filename), new FileInfo(string.Format("{0}.down", filename))); + }); + + var result = from file in uploadFilenames + where CalculateMD5(file) == CalculateMD5(string.Format("{0}.down", file)) + select file; + + scp.Disconnect(); + + Assert.IsTrue(result.Count() == uploadFilenames.Length); + } + } + + [TestMethod] + [TestCategory("Scp")] + public void Test_Scp_File_Upload_Download_Events() + { + using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + scp.Connect(); + + var uploadFilenames = new string[10]; + + for (var i = 0; i < uploadFilenames.Length; i++) + { + uploadFilenames[i] = Path.GetTempFileName(); + CreateTestFile(uploadFilenames[i], 1); + } + + var uploadedFiles = uploadFilenames.ToDictionary(Path.GetFileName, (filename) => 0L); + var downloadedFiles = uploadFilenames.ToDictionary((filename) => string.Format("{0}.down", Path.GetFileName(filename)), (filename) => 0L); + + scp.Uploading += delegate (object sender, ScpUploadEventArgs e) + { + uploadedFiles[e.Filename] = e.Uploaded; + }; + + scp.Downloading += delegate (object sender, ScpDownloadEventArgs e) + { + downloadedFiles[string.Format("{0}.down", e.Filename)] = e.Downloaded; + }; + + _ = Parallel.ForEach(uploadFilenames, + filename => + { + scp.Upload(new FileInfo(filename), Path.GetFileName(filename)); + }); + _ = Parallel.ForEach(uploadFilenames, + filename => + { + scp.Download(Path.GetFileName(filename), new FileInfo(string.Format("{0}.down", filename))); + }); + + var result = from uf in uploadedFiles + from df in downloadedFiles + where string.Format("{0}.down", uf.Key) == df.Key && uf.Value == df.Value + select uf; + + scp.Disconnect(); + + Assert.IsTrue(result.Count() == uploadFilenames.Length && uploadFilenames.Length == uploadedFiles.Count && uploadedFiles.Count == downloadedFiles.Count); + } + } + + protected static string CalculateMD5(string fileName) + { + using (var file = new FileStream(fileName, FileMode.Open)) + { +#if NET7_0_OR_GREATER + var hash = MD5.HashData(file); +#else +#if NET6_0 + var md5 = MD5.Create(); +#else + MD5 md5 = new MD5CryptoServiceProvider(); +#endif // NET6_0 + var hash = md5.ComputeHash(file); +#endif // NET7_0_OR_GREATER + + file.Close(); + + var sb = new StringBuilder(); + + for (var i = 0; i < hash.Length; i++) + { + _ = sb.Append(i.ToString("x2")); + } + + return sb.ToString(); + } + } + + private void RemoveAllFiles() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + _ = client.RunCommand("rm -rf *"); + client.Disconnect(); + } + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.ChangeDirectory.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs similarity index 66% rename from src/Renci.SshNet.Tests/Classes/SftpClientTest.ChangeDirectory.cs rename to src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs index 5c58e1fa1..6fb518506 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.ChangeDirectory.cs +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs @@ -1,21 +1,18 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Common; -using Renci.SshNet.Tests.Properties; +using Renci.SshNet.Common; -namespace Renci.SshNet.Tests.Classes +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests { /// /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. /// - public partial class SftpClientTest + public partial class SftpClientTest : IntegrationTestBase { [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [ExpectedException(typeof(SftpPathNotFoundException))] public void Test_Sftp_ChangeDirectory_Root_Dont_Exists() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); sftp.ChangeDirectory("/asdasd"); @@ -24,11 +21,10 @@ public void Test_Sftp_ChangeDirectory_Root_Dont_Exists() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [ExpectedException(typeof(SftpPathNotFoundException))] public void Test_Sftp_ChangeDirectory_Root_With_Slash_Dont_Exists() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); sftp.ChangeDirectory("/asdasd/"); @@ -37,11 +33,10 @@ public void Test_Sftp_ChangeDirectory_Root_With_Slash_Dont_Exists() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [ExpectedException(typeof(SftpPathNotFoundException))] public void Test_Sftp_ChangeDirectory_Subfolder_Dont_Exists() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); sftp.ChangeDirectory("/asdasd/sssddds"); @@ -50,11 +45,10 @@ public void Test_Sftp_ChangeDirectory_Subfolder_Dont_Exists() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [ExpectedException(typeof(SftpPathNotFoundException))] public void Test_Sftp_ChangeDirectory_Subfolder_With_Slash_Dont_Exists() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); sftp.ChangeDirectory("/asdasd/sssddds/"); @@ -63,10 +57,9 @@ public void Test_Sftp_ChangeDirectory_Subfolder_With_Slash_Dont_Exists() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] public void Test_Sftp_ChangeDirectory_Which_Exists() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); sftp.ChangeDirectory("/usr"); @@ -76,10 +69,9 @@ public void Test_Sftp_ChangeDirectory_Which_Exists() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] public void Test_Sftp_ChangeDirectory_Which_Exists_With_Slash() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); sftp.ChangeDirectory("/usr/"); @@ -87,4 +79,4 @@ public void Test_Sftp_ChangeDirectory_Which_Exists_With_Slash() } } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.CreateDirectory.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.CreateDirectory.cs new file mode 100644 index 000000000..43b176697 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.CreateDirectory.cs @@ -0,0 +1,72 @@ + +using Renci.SshNet.Common; + +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests +{ + /// + /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. + /// + public partial class SftpClientTest : IntegrationTestBase + { + [TestMethod] + [TestCategory("Sftp")] + public void Test_Sftp_CreateDirectory_In_Current_Location() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + sftp.CreateDirectory("test-in-current"); + + sftp.Disconnect(); + } + } + + [TestMethod] + [TestCategory("Sftp")] + [ExpectedException(typeof(SftpPermissionDeniedException))] + public void Test_Sftp_CreateDirectory_In_Forbidden_Directory() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, AdminUser.UserName, AdminUser.Password)) + { + sftp.Connect(); + + sftp.CreateDirectory("/sbin/test"); + + sftp.Disconnect(); + } + } + + [TestMethod] + [TestCategory("Sftp")] + [ExpectedException(typeof(SftpPathNotFoundException))] + public void Test_Sftp_CreateDirectory_Invalid_Path() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + sftp.CreateDirectory("/abcdefg/abcefg"); + + sftp.Disconnect(); + } + } + + [TestMethod] + [TestCategory("Sftp")] + [ExpectedException(typeof(SshException))] + public void Test_Sftp_CreateDirectory_Already_Exists() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + sftp.CreateDirectory("test"); + + sftp.CreateDirectory("test"); + + sftp.Disconnect(); + } + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.DeleteDirectory.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.DeleteDirectory.cs new file mode 100644 index 000000000..30697ddce --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.DeleteDirectory.cs @@ -0,0 +1,71 @@ +using Renci.SshNet.Common; + +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests +{ + /// + /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. + /// + public partial class SftpClientTest : IntegrationTestBase + { + [TestMethod] + [TestCategory("Sftp")] + [ExpectedException(typeof(SftpPathNotFoundException))] + public void Test_Sftp_DeleteDirectory_Which_Doesnt_Exists() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + sftp.DeleteDirectory("abcdef"); + + sftp.Disconnect(); + } + } + + [TestMethod] + [TestCategory("Sftp")] + [ExpectedException(typeof(SftpPermissionDeniedException))] + public void Test_Sftp_DeleteDirectory_Which_No_Permissions() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, AdminUser.UserName, AdminUser.Password)) + { + sftp.Connect(); + + sftp.DeleteDirectory("/usr"); + + sftp.Disconnect(); + } + } + + [TestMethod] + [TestCategory("Sftp")] + public void Test_Sftp_DeleteDirectory() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + sftp.CreateDirectory("abcdef"); + sftp.DeleteDirectory("abcdef"); + + sftp.Disconnect(); + } + } + + [TestMethod] + [TestCategory("Sftp")] + [Description("Test passing null to DeleteDirectory.")] + [ExpectedException(typeof(ArgumentException))] + public void Test_Sftp_DeleteDirectory_Null() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + sftp.DeleteDirectory(null); + + sftp.Disconnect(); + } + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.Download.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Download.cs similarity index 70% rename from src/Renci.SshNet.Tests/Classes/SftpClientTest.Download.cs rename to src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Download.cs index 41dbdf33f..318834e4c 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.Download.cs +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Download.cs @@ -1,28 +1,18 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Common; -using Renci.SshNet.Tests.Properties; -using System; -using System.IO; +using Renci.SshNet.Common; -namespace Renci.SshNet.Tests.Classes +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests { /// /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. /// - public partial class SftpClientTest + public partial class SftpClientTest : IntegrationTestBase { [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [ExpectedException(typeof(SftpPermissionDeniedException))] public void Test_Sftp_Download_Forbidden() { - if (Resources.USERNAME == "root") - { - Assert.Fail("Must not run this test as root!"); - } - - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, AdminUser.UserName, AdminUser.Password)) { sftp.Connect(); @@ -39,11 +29,10 @@ public void Test_Sftp_Download_Forbidden() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [ExpectedException(typeof(SftpPathNotFoundException))] public void Test_Sftp_Download_File_Not_Exists() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); @@ -59,12 +48,11 @@ public void Test_Sftp_Download_File_Not_Exists() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [Description("Test passing null to BeginDownloadFile")] [ExpectedException(typeof(ArgumentNullException))] public void Test_Sftp_BeginDownloadFile_StreamIsNull() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); sftp.BeginDownloadFile("aaaa", null, null, null); @@ -74,12 +62,11 @@ public void Test_Sftp_BeginDownloadFile_StreamIsNull() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [Description("Test passing null to BeginDownloadFile")] [ExpectedException(typeof(ArgumentException))] public void Test_Sftp_BeginDownloadFile_FileNameIsWhiteSpace() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); sftp.BeginDownloadFile(" ", new MemoryStream(), null, null); @@ -89,12 +76,11 @@ public void Test_Sftp_BeginDownloadFile_FileNameIsWhiteSpace() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [Description("Test passing null to BeginDownloadFile")] [ExpectedException(typeof(ArgumentException))] public void Test_Sftp_BeginDownloadFile_FileNameIsNull() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); sftp.BeginDownloadFile(null, new MemoryStream(), null, null); @@ -104,15 +90,14 @@ public void Test_Sftp_BeginDownloadFile_FileNameIsNull() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [ExpectedException(typeof(ArgumentException))] public void Test_Sftp_EndDownloadFile_Invalid_Async_Handle() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); var filename = Path.GetTempFileName(); - this.CreateTestFile(filename, 1); + CreateTestFile(filename, 1); sftp.UploadFile(File.OpenRead(filename), "test123"); var async1 = sftp.BeginListDirectory("/", null, null); var async2 = sftp.BeginDownloadFile("test123", new MemoryStream(), null, null); diff --git a/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs new file mode 100644 index 000000000..e37e937f9 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs @@ -0,0 +1,263 @@ +using System.Diagnostics; + +using Renci.SshNet.Common; + +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests +{ + /// + /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. + /// + public partial class SftpClientTest : IntegrationTestBase + { + [TestMethod] + [TestCategory("Sftp")] + [ExpectedException(typeof(SftpPermissionDeniedException))] + public void Test_Sftp_ListDirectory_Permission_Denied() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + var files = sftp.ListDirectory("/root"); + foreach (var file in files) + { + Debug.WriteLine(file.FullName); + } + + sftp.Disconnect(); + } + } + + [TestMethod] + [TestCategory("Sftp")] + [ExpectedException(typeof(SftpPathNotFoundException))] + public void Test_Sftp_ListDirectory_Not_Exists() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + var files = sftp.ListDirectory("/asdfgh"); + foreach (var file in files) + { + Debug.WriteLine(file.FullName); + } + + sftp.Disconnect(); + } + } + + [TestMethod] + [TestCategory("Sftp")] + public void Test_Sftp_ListDirectory_Current() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + var files = sftp.ListDirectory("."); + + Assert.IsTrue(files.Count() > 0); + + foreach (var file in files) + { + Debug.WriteLine(file.FullName); + } + + sftp.Disconnect(); + } + } + +#if NET6_0_OR_GREATER + [TestMethod] + [TestCategory("Sftp")] + public async Task Test_Sftp_ListDirectoryAsync_Current() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromMinutes(1)); + var count = 0; + await foreach(var file in sftp.ListDirectoryAsync(".", cts.Token)) + { + count++; + Debug.WriteLine(file.FullName); + } + + Assert.IsTrue(count > 0); + + sftp.Disconnect(); + } + } +#endif + [TestMethod] + [TestCategory("Sftp")] + public void Test_Sftp_ListDirectory_Empty() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + var files = sftp.ListDirectory(string.Empty); + + Assert.IsTrue(files.Count() > 0); + + foreach (var file in files) + { + Debug.WriteLine(file.FullName); + } + + sftp.Disconnect(); + } + } + + [TestMethod] + [TestCategory("Sftp")] + [Description("Test passing null to ListDirectory.")] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_Sftp_ListDirectory_Null() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + var files = sftp.ListDirectory(null); + + Assert.IsTrue(files.Count() > 0); + + foreach (var file in files) + { + Debug.WriteLine(file.FullName); + } + + sftp.Disconnect(); + } + } + + [TestMethod] + [TestCategory("Sftp")] + public void Test_Sftp_ListDirectory_HugeDirectory() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + // Create 10000 directory items + for (int i = 0; i < 10000; i++) + { + sftp.CreateDirectory(string.Format("test_{0}", i)); + } + + var files = sftp.ListDirectory("."); + + // Ensure that directory has at least 10000 items + Assert.IsTrue(files.Count() > 10000); + + sftp.Disconnect(); + } + + RemoveAllFiles(); + } + + [TestMethod] + [TestCategory("Sftp")] + public void Test_Sftp_Change_Directory() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet"); + + sftp.CreateDirectory("test1"); + + sftp.ChangeDirectory("test1"); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1"); + + sftp.CreateDirectory("test1_1"); + sftp.CreateDirectory("test1_2"); + sftp.CreateDirectory("test1_3"); + + var files = sftp.ListDirectory("."); + + Assert.IsTrue(files.First().FullName.StartsWith(string.Format("{0}", sftp.WorkingDirectory))); + + sftp.ChangeDirectory("test1_1"); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1"); + + sftp.ChangeDirectory("../test1_2"); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_2"); + + sftp.ChangeDirectory(".."); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1"); + + sftp.ChangeDirectory(".."); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet"); + + files = sftp.ListDirectory("test1/test1_1"); + + Assert.IsTrue(files.First().FullName.StartsWith(string.Format("{0}/test1/test1_1", sftp.WorkingDirectory))); + + sftp.ChangeDirectory("test1/test1_1"); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1"); + + sftp.ChangeDirectory("/home/sshnet/test1/test1_1"); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1"); + + sftp.ChangeDirectory("/home/sshnet/test1/test1_1/../test1_2"); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_2"); + + sftp.ChangeDirectory("../../"); + + sftp.DeleteDirectory("test1/test1_1"); + sftp.DeleteDirectory("test1/test1_2"); + sftp.DeleteDirectory("test1/test1_3"); + sftp.DeleteDirectory("test1"); + + sftp.Disconnect(); + } + + RemoveAllFiles(); + } + + [TestMethod] + [TestCategory("Sftp")] + [Description("Test passing null to ChangeDirectory.")] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_Sftp_ChangeDirectory_Null() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + sftp.ChangeDirectory(null); + + sftp.Disconnect(); + } + } + + [TestMethod] + [TestCategory("Sftp")] + [Description("Test calling EndListDirectory method more then once.")] + [ExpectedException(typeof(ArgumentException))] + public void Test_Sftp_Call_EndListDirectory_Twice() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + var ar = sftp.BeginListDirectory("/", null, null); + var result = sftp.EndListDirectory(ar); + var result1 = sftp.EndListDirectory(ar); + } + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.RenameFile.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.RenameFile.cs similarity index 69% rename from src/Renci.SshNet.Tests/Classes/SftpClientTest.RenameFile.cs rename to src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.RenameFile.cs index e1685d099..e74a8e7ed 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.RenameFile.cs +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.RenameFile.cs @@ -1,21 +1,15 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Tests.Properties; -using System; -using System.IO; - -namespace Renci.SshNet.Tests.Classes +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests { /// /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. /// - public partial class SftpClientTest + public partial class SftpClientTest : IntegrationTestBase { [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] public void Test_Sftp_Rename_File() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); @@ -23,7 +17,7 @@ public void Test_Sftp_Rename_File() string remoteFileName1 = Path.GetRandomFileName(); string remoteFileName2 = Path.GetRandomFileName(); - this.CreateTestFile(uploadedFileName, 1); + CreateTestFile(uploadedFileName, 1); using (var file = File.OpenRead(uploadedFileName)) { @@ -42,12 +36,11 @@ public void Test_Sftp_Rename_File() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [Description("Test passing null to RenameFile.")] [ExpectedException(typeof(ArgumentNullException))] public void Test_Sftp_RenameFile_Null() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); @@ -57,4 +50,4 @@ public void Test_Sftp_RenameFile_Null() } } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.RenameFileAsync.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.RenameFileAsync.cs similarity index 72% rename from src/Renci.SshNet.Tests/Classes/SftpClientTest.RenameFileAsync.cs rename to src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.RenameFileAsync.cs index a24ec0942..ade91099c 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.RenameFileAsync.cs +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.RenameFileAsync.cs @@ -1,22 +1,15 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Tests.Properties; -using System; -using System.IO; -using System.Threading.Tasks; - -namespace Renci.SshNet.Tests.Classes +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests { /// /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. /// - public partial class SftpClientTest + public partial class SftpClientTest : IntegrationTestBase { [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] public async Task Test_Sftp_RenameFileAsync() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { await sftp.ConnectAsync(default); @@ -24,7 +17,7 @@ public async Task Test_Sftp_RenameFileAsync() string remoteFileName1 = Path.GetRandomFileName(); string remoteFileName2 = Path.GetRandomFileName(); - this.CreateTestFile(uploadedFileName, 1); + CreateTestFile(uploadedFileName, 1); using (var file = File.OpenRead(uploadedFileName)) { @@ -46,12 +39,11 @@ public async Task Test_Sftp_RenameFileAsync() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [Description("Test passing null to RenameFile.")] [ExpectedException(typeof(ArgumentNullException))] public async Task Test_Sftp_RenameFileAsync_Null() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { await sftp.ConnectAsync(default); diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.SynchronizeDirectories.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.SynchronizeDirectories.cs similarity index 78% rename from src/Renci.SshNet.Tests/Classes/SftpClientTest.SynchronizeDirectories.cs rename to src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.SynchronizeDirectories.cs index 2c9ddb3e0..4964715ef 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.SynchronizeDirectories.cs +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.SynchronizeDirectories.cs @@ -1,31 +1,26 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Tests.Properties; -using System.Diagnostics; -using System.IO; -using System.Linq; +using System.Diagnostics; -namespace Renci.SshNet.Tests.Classes +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests { /// /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. /// - public partial class SftpClientTest + public partial class SftpClientTest : IntegrationTestBase { [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] public void Test_Sftp_SynchronizeDirectories() { RemoveAllFiles(); - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); string uploadedFileName = Path.GetTempFileName(); string sourceDir = Path.GetDirectoryName(uploadedFileName); - string destDir = "/"; + string destDir = "/home/sshnet/"; string searchPattern = Path.GetFileName(uploadedFileName); var upLoadedFiles = sftp.SynchronizeDirectories(sourceDir, destDir, searchPattern); @@ -42,19 +37,18 @@ public void Test_Sftp_SynchronizeDirectories() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] public void Test_Sftp_BeginSynchronizeDirectories() { RemoveAllFiles(); - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); string uploadedFileName = Path.GetTempFileName(); string sourceDir = Path.GetDirectoryName(uploadedFileName); - string destDir = "/"; + string destDir = "/home/sshnet/"; string searchPattern = Path.GetFileName(uploadedFileName); var asyncResult = sftp.BeginSynchronizeDirectories(sourceDir, @@ -83,4 +77,4 @@ public void Test_Sftp_BeginSynchronizeDirectories() } } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.Upload.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Upload.cs similarity index 89% rename from src/Renci.SshNet.Tests/Classes/SftpClientTest.Upload.cs rename to src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Upload.cs index 2018c9f0d..91c248bd3 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.Upload.cs +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Upload.cs @@ -1,29 +1,20 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -using Renci.SshNet.Common; +using Renci.SshNet.Common; using Renci.SshNet.Sftp; -using Renci.SshNet.Tests.Properties; -namespace Renci.SshNet.Tests.Classes +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests { /// /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. /// - public partial class SftpClientTest + public partial class SftpClientTest : IntegrationTestBase { [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] public void Test_Sftp_Upload_And_Download_1MB_File() { RemoveAllFiles(); - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); @@ -62,11 +53,10 @@ public void Test_Sftp_Upload_And_Download_1MB_File() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [ExpectedException(typeof(SftpPermissionDeniedException))] public void Test_Sftp_Upload_Forbidden() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); @@ -86,7 +76,6 @@ public void Test_Sftp_Upload_Forbidden() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] public void Test_Sftp_Multiple_Async_Upload_And_Download_10Files_5MB_Each() { var maxFiles = 10; @@ -94,8 +83,9 @@ public void Test_Sftp_Multiple_Async_Upload_And_Download_10Files_5MB_Each() RemoveAllFiles(); - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { + sftp.OperationTimeout = TimeSpan.FromMinutes(1); sftp.Connect(); var testInfoList = new Dictionary(); @@ -210,6 +200,11 @@ public void Test_Sftp_Multiple_Async_Upload_And_Download_10Files_5MB_Each() testInfo.DownloadedHash = CalculateMD5(testInfo.DownloadedFileName); + Console.WriteLine(remoteFile); + Console.WriteLine("UploadedBytes: "+ testInfo.UploadResult.UploadedBytes); + Console.WriteLine("DownloadedBytes: " + testInfo.DownloadResult.DownloadedBytes); + Console.WriteLine("UploadedHash: " + testInfo.UploadedHash); + Console.WriteLine("DownloadedHash: " + testInfo.DownloadedHash); if (!(testInfo.UploadResult.UploadedBytes > 0 && testInfo.DownloadResult.DownloadedBytes > 0 && testInfo.DownloadResult.DownloadedBytes == testInfo.UploadResult.UploadedBytes)) { uploadDownloadSizeOk = false; @@ -242,13 +237,12 @@ public void Test_Sftp_Multiple_Async_Upload_And_Download_10Files_5MB_Each() // TODO: Split this test into multiple tests [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [Description("Test that delegates passed to BeginUploadFile, BeginDownloadFile and BeginListDirectory are actually called.")] public void Test_Sftp_Ensure_Async_Delegates_Called_For_BeginFileUpload_BeginFileDownload_BeginListDirectory() { RemoveAllFiles(); - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); @@ -330,12 +324,11 @@ public void Test_Sftp_Ensure_Async_Delegates_Called_For_BeginFileUpload_BeginFil [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [Description("Test passing null to BeginUploadFile")] [ExpectedException(typeof(ArgumentNullException))] public void Test_Sftp_BeginUploadFile_StreamIsNull() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); _ = sftp.BeginUploadFile(null, "aaaaa", null, null); @@ -345,12 +338,11 @@ public void Test_Sftp_BeginUploadFile_StreamIsNull() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [Description("Test passing null to BeginUploadFile")] [ExpectedException(typeof(ArgumentException))] public void Test_Sftp_BeginUploadFile_FileNameIsWhiteSpace() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); _ = sftp.BeginUploadFile(new MemoryStream(), " ", null, null); @@ -360,12 +352,11 @@ public void Test_Sftp_BeginUploadFile_FileNameIsWhiteSpace() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [Description("Test passing null to BeginUploadFile")] [ExpectedException(typeof(ArgumentException))] public void Test_Sftp_BeginUploadFile_FileNameIsNull() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); _ = sftp.BeginUploadFile(new MemoryStream(), null, null, null); @@ -375,11 +366,10 @@ public void Test_Sftp_BeginUploadFile_FileNameIsNull() [TestMethod] [TestCategory("Sftp")] - [TestCategory("integration")] [ExpectedException(typeof(ArgumentException))] public void Test_Sftp_EndUploadFile_Invalid_Async_Handle() { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { sftp.Connect(); var async1 = sftp.BeginListDirectory("/", null, null); diff --git a/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.cs new file mode 100644 index 000000000..1c7def55b --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.cs @@ -0,0 +1,68 @@ +using System.Security.Cryptography; + +using Renci.SshNet.Sftp; + +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests +{ + /// + /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. + /// + [TestClass] + public partial class SftpClientTest + { + protected static string CalculateMD5(string fileName) + { + using (FileStream file = new FileStream(fileName, FileMode.Open)) + { + var hash = MD5.HashData(file); + + file.Close(); + + StringBuilder sb = new StringBuilder(); + for (var i = 0; i < hash.Length; i++) + { + sb.Append(hash[i].ToString("x2")); + } + return sb.ToString(); + } + } + + private void RemoveAllFiles() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + client.RunCommand("rm -rf *"); + client.Disconnect(); + } + } + + /// + /// Helper class to help with upload and download testing + /// + private class TestInfo + { + public string RemoteFileName { get; set; } + + public string UploadedFileName { get; set; } + + public string DownloadedFileName { get; set; } + + //public ulong UploadedBytes { get; set; } + + //public ulong DownloadedBytes { get; set; } + + public FileStream UploadedFile { get; set; } + + public FileStream DownloadedFile { get; set; } + + public string UploadedHash { get; set; } + + public string DownloadedHash { get; set; } + + public SftpUploadAsyncResult UploadResult { get; set; } + + public SftpDownloadAsyncResult DownloadResult { get; set; } + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs new file mode 100644 index 000000000..058442511 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs @@ -0,0 +1,120 @@ +using Renci.SshNet.Common; + +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests +{ + /// + /// Represents SFTP file information + /// + [TestClass] + public class SftpFileTest : IntegrationTestBase + { + [TestMethod] + [TestCategory("Sftp")] + public void Test_Get_Root_Directory() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + var directory = sftp.Get("/"); + + Assert.AreEqual("/", directory.FullName); + Assert.IsTrue(directory.IsDirectory); + Assert.IsFalse(directory.IsRegularFile); + } + } + + [TestMethod] + [TestCategory("Sftp")] + [ExpectedException(typeof(SftpPathNotFoundException))] + public void Test_Get_Invalid_Directory() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + sftp.Get("/xyz"); + } + } + + [TestMethod] + [TestCategory("Sftp")] + public void Test_Get_File() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + sftp.UploadFile(new MemoryStream(), "abc.txt"); + + var file = sftp.Get("abc.txt"); + + Assert.AreEqual("/home/sshnet/abc.txt", file.FullName); + Assert.IsTrue(file.IsRegularFile); + Assert.IsFalse(file.IsDirectory); + } + } + + [TestMethod] + [TestCategory("Sftp")] + [Description("Test passing null to Get.")] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_Get_File_Null() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + var file = sftp.Get(null); + + sftp.Disconnect(); + } + } + + [TestMethod] + [TestCategory("Sftp")] + public void Test_Get_International_File() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + sftp.UploadFile(new MemoryStream(), "test-üöä-"); + + var file = sftp.Get("test-üöä-"); + + Assert.AreEqual("/home/sshnet/test-üöä-", file.FullName); + Assert.IsTrue(file.IsRegularFile); + Assert.IsFalse(file.IsDirectory); + } + } + + [TestMethod] + [TestCategory("Sftp")] + public void Test_Sftp_SftpFile_MoveTo() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + string uploadedFileName = Path.GetTempFileName(); + string remoteFileName = Path.GetRandomFileName(); + string newFileName = Path.GetRandomFileName(); + + CreateTestFile(uploadedFileName, 1); + + using (var file = File.OpenRead(uploadedFileName)) + { + sftp.UploadFile(file, remoteFileName); + } + + var sftpFile = sftp.Get(remoteFileName); + + sftpFile.MoveTo(newFileName); + + Assert.AreEqual(newFileName, sftpFile.Name); + + sftp.Disconnect(); + } + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SshCommandTest.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SshCommandTest.cs new file mode 100644 index 000000000..d852a093a --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SshCommandTest.cs @@ -0,0 +1,545 @@ +using System.Diagnostics; + +using Renci.SshNet.Common; + +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests +{ + /// + /// Represents SSH command that can be executed. + /// + [TestClass] + public class SshCommandTest : IntegrationTestBase + { + [TestMethod] + public void Test_Run_SingleCommand() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + #region Example SshCommand RunCommand Result + client.Connect(); + + var testValue = Guid.NewGuid().ToString(); + var command = client.RunCommand(string.Format("echo {0}", testValue)); + var result = command.Result; + result = result.Substring(0, result.Length - 1); // Remove \n character returned by command + + client.Disconnect(); + #endregion + + Assert.IsTrue(result.Equals(testValue)); + } + } + + [TestMethod] + public void Test_Execute_SingleCommand() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + #region Example SshCommand CreateCommand Execute + client.Connect(); + + var testValue = Guid.NewGuid().ToString(); + var command = string.Format("echo {0}", testValue); + var cmd = client.CreateCommand(command); + var result = cmd.Execute(); + result = result.Substring(0, result.Length - 1); // Remove \n character returned by command + + client.Disconnect(); + #endregion + + Assert.IsTrue(result.Equals(testValue)); + } + } + + [TestMethod] + public void Test_Execute_OutputStream() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + #region Example SshCommand CreateCommand Execute OutputStream + client.Connect(); + + var cmd = client.CreateCommand("ls -l"); // very long list + var asynch = cmd.BeginExecute(); + + var reader = new StreamReader(cmd.OutputStream); + + while (!asynch.IsCompleted) + { + var result = reader.ReadToEnd(); + if (string.IsNullOrEmpty(result)) + { + continue; + } + + Console.Write(result); + } + + _ = cmd.EndExecute(asynch); + + client.Disconnect(); + #endregion + + Assert.Inconclusive(); + } + } + + [TestMethod] + public void Test_Execute_ExtendedOutputStream() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + #region Example SshCommand CreateCommand Execute ExtendedOutputStream + + client.Connect(); + var cmd = client.CreateCommand("echo 12345; echo 654321 >&2"); + var result = cmd.Execute(); + + Console.Write(result); + + var reader = new StreamReader(cmd.ExtendedOutputStream); + Console.WriteLine("DEBUG:"); + Console.Write(reader.ReadToEnd()); + + client.Disconnect(); + + #endregion + + Assert.Inconclusive(); + } + } + + [TestMethod] + [ExpectedException(typeof(SshOperationTimeoutException))] + public void Test_Execute_Timeout() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + #region Example SshCommand CreateCommand Execute CommandTimeout + client.Connect(); + var cmd = client.CreateCommand("sleep 10s"); + cmd.CommandTimeout = TimeSpan.FromSeconds(5); + cmd.Execute(); + client.Disconnect(); + #endregion + } + } + + [TestMethod] + public void Test_Execute_Infinite_Timeout() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + var cmd = client.CreateCommand("sleep 10s"); + cmd.Execute(); + client.Disconnect(); + } + } + + [TestMethod] + public void Test_Execute_InvalidCommand() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + + var cmd = client.CreateCommand(";"); + cmd.Execute(); + if (string.IsNullOrEmpty(cmd.Error)) + { + Assert.Fail("Operation should fail"); + } + Assert.IsTrue(cmd.ExitStatus > 0); + + client.Disconnect(); + } + } + + [TestMethod] + public void Test_Execute_InvalidCommand_Then_Execute_ValidCommand() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + var cmd = client.CreateCommand(";"); + cmd.Execute(); + if (string.IsNullOrEmpty(cmd.Error)) + { + Assert.Fail("Operation should fail"); + } + Assert.IsTrue(cmd.ExitStatus > 0); + + var result = ExecuteTestCommand(client); + + client.Disconnect(); + + Assert.IsTrue(result); + } + } + + [TestMethod] + public void Test_Execute_Command_with_ExtendedOutput() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + var cmd = client.CreateCommand("echo 12345; echo 654321 >&2"); + cmd.Execute(); + + //var extendedData = Encoding.ASCII.GetString(cmd.ExtendedOutputStream.ToArray()); + var extendedData = new StreamReader(cmd.ExtendedOutputStream, Encoding.ASCII).ReadToEnd(); + client.Disconnect(); + + Assert.AreEqual("12345\n", cmd.Result); + Assert.AreEqual("654321\n", extendedData); + } + } + + [TestMethod] + public void Test_Execute_Command_Reconnect_Execute_Command() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + var result = ExecuteTestCommand(client); + Assert.IsTrue(result); + + client.Disconnect(); + client.Connect(); + result = ExecuteTestCommand(client); + Assert.IsTrue(result); + client.Disconnect(); + } + } + + [TestMethod] + public void Test_Execute_Command_ExitStatus() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + #region Example SshCommand RunCommand ExitStatus + client.Connect(); + + var cmd = client.RunCommand("exit 128"); + + Console.WriteLine(cmd.ExitStatus); + + client.Disconnect(); + #endregion + + Assert.IsTrue(cmd.ExitStatus == 128); + } + } + + [TestMethod] + public void Test_Execute_Command_Asynchronously() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + + var cmd = client.CreateCommand("sleep 5s; echo 'test'"); + var asyncResult = cmd.BeginExecute(null, null); + while (!asyncResult.IsCompleted) + { + Thread.Sleep(100); + } + + cmd.EndExecute(asyncResult); + + Assert.IsTrue(cmd.Result == "test\n"); + + client.Disconnect(); + } + } + + [TestMethod] + public void Test_Execute_Command_Asynchronously_With_Error() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + + var cmd = client.CreateCommand("sleep 5s; ;"); + var asyncResult = cmd.BeginExecute(null, null); + while (!asyncResult.IsCompleted) + { + Thread.Sleep(100); + } + + cmd.EndExecute(asyncResult); + + Assert.IsFalse(string.IsNullOrEmpty(cmd.Error)); + + client.Disconnect(); + } + } + + [TestMethod] + public void Test_Execute_Command_Asynchronously_With_Callback() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + + var callbackCalled = false; + + var cmd = client.CreateCommand("sleep 5s; echo 'test'"); + var asyncResult = cmd.BeginExecute(new AsyncCallback((s) => + { + callbackCalled = true; + }), null); + while (!asyncResult.IsCompleted) + { + Thread.Sleep(100); + } + + cmd.EndExecute(asyncResult); + + Assert.IsTrue(callbackCalled); + + client.Disconnect(); + } + } + + [TestMethod] + public void Test_Execute_Command_Asynchronously_With_Callback_On_Different_Thread() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + + var currentThreadId = Thread.CurrentThread.ManagedThreadId; + int callbackThreadId = 0; + + var cmd = client.CreateCommand("sleep 5s; echo 'test'"); + var asyncResult = cmd.BeginExecute(new AsyncCallback((s) => + { + callbackThreadId = Thread.CurrentThread.ManagedThreadId; + }), null); + while (!asyncResult.IsCompleted) + { + Thread.Sleep(100); + } + + cmd.EndExecute(asyncResult); + + Assert.AreNotEqual(currentThreadId, callbackThreadId); + + client.Disconnect(); + } + } + + /// + /// Tests for Issue 563. + /// + [WorkItem(563), TestMethod] + public void Test_Execute_Command_Same_Object_Different_Commands() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + var cmd = client.CreateCommand("echo 12345"); + cmd.Execute(); + Assert.AreEqual("12345\n", cmd.Result); + cmd.Execute("echo 23456"); + Assert.AreEqual("23456\n", cmd.Result); + client.Disconnect(); + } + } + + [TestMethod] + public void Test_Get_Result_Without_Execution() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + var cmd = client.CreateCommand("ls -l"); + + Assert.IsTrue(string.IsNullOrEmpty(cmd.Result)); + client.Disconnect(); + } + } + + [TestMethod] + public void Test_Get_Error_Without_Execution() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + var cmd = client.CreateCommand("ls -l"); + + Assert.IsTrue(string.IsNullOrEmpty(cmd.Error)); + client.Disconnect(); + } + } + + [WorkItem(703), TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_EndExecute_Before_BeginExecute() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + var cmd = client.CreateCommand("ls -l"); + cmd.EndExecute(null); + client.Disconnect(); + } + } + + /// + ///A test for BeginExecute + /// + [TestMethod()] + public void BeginExecuteTest() + { + string expected = "123\n"; + string result; + + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + #region Example SshCommand CreateCommand BeginExecute IsCompleted EndExecute + + client.Connect(); + + var cmd = client.CreateCommand("sleep 15s;echo 123"); // Perform long running task + + var asynch = cmd.BeginExecute(); + + while (!asynch.IsCompleted) + { + // Waiting for command to complete... + Thread.Sleep(2000); + } + result = cmd.EndExecute(asynch); + client.Disconnect(); + + #endregion + + Assert.IsNotNull(asynch); + Assert.AreEqual(expected, result); + } + } + + [TestMethod] + public void Test_Execute_Invalid_Command() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + #region Example SshCommand CreateCommand Error + + client.Connect(); + + var cmd = client.CreateCommand(";"); + cmd.Execute(); + if (!string.IsNullOrEmpty(cmd.Error)) + { + Console.WriteLine(cmd.Error); + } + + client.Disconnect(); + + #endregion + + Assert.Inconclusive(); + } + } + + [TestMethod] + public void Test_MultipleThread_Example_MultipleConnections() + { + try + { +#region Example SshCommand RunCommand Parallel + Parallel.For(0, 100, + () => + { + var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); + client.Connect(); + return client; + }, + (int counter, ParallelLoopState pls, SshClient client) => + { + var result = client.RunCommand("echo 123"); + Debug.WriteLine(string.Format("TestMultipleThreadMultipleConnections #{0}", counter)); + return client; + }, + (SshClient client) => + { + client.Disconnect(); + client.Dispose(); + } + ); +#endregion + + } + catch (Exception exp) + { + Assert.Fail(exp.ToString()); + } + } + + [TestMethod] + + public void Test_MultipleThread_100_MultipleConnections() + { + try + { + Parallel.For(0, 100, + () => + { + var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); + client.Connect(); + return client; + }, + (int counter, ParallelLoopState pls, SshClient client) => + { + var result = ExecuteTestCommand(client); + Debug.WriteLine(string.Format("TestMultipleThreadMultipleConnections #{0}", counter)); + Assert.IsTrue(result); + return client; + }, + (SshClient client) => + { + client.Disconnect(); + client.Dispose(); + } + ); + } + catch (Exception exp) + { + Assert.Fail(exp.ToString()); + } + } + + [TestMethod] + public void Test_MultipleThread_100_MultipleSessions() + { + using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + client.Connect(); + Parallel.For(0, 100, + (counter) => + { + var result = ExecuteTestCommand(client); + Debug.WriteLine(string.Format("TestMultipleThreadMultipleConnections #{0}", counter)); + Assert.IsTrue(result); + } + ); + + client.Disconnect(); + } + } + + private static bool ExecuteTestCommand(SshClient s) + { + var testValue = Guid.NewGuid().ToString(); + var command = string.Format("echo {0}", testValue); + var cmd = s.CreateCommand(command); + var result = cmd.Execute(); + result = result.Substring(0, result.Length - 1); // Remove \n character returned by command + return result.Equals(testValue); + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/TripleDesCipherTest.cs b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/TripleDesCipherTest.cs new file mode 100644 index 000000000..a9195cf5e --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/OldIntegrationTests/TripleDesCipherTest.cs @@ -0,0 +1,50 @@ +using Renci.SshNet.IntegrationTests.Common; +using Renci.SshNet.Security.Cryptography.Ciphers; +using Renci.SshNet.Security.Cryptography.Ciphers.Modes; +using Renci.SshNet.TestTools.OpenSSH; + +namespace Renci.SshNet.IntegrationTests.OldIntegrationTests +{ + /// + /// Implements 3DES cipher algorithm. + /// + [TestClass] + public class TripleDesCipherTest : IntegrationTestBase + { + private IConnectionInfoFactory _adminConnectionInfoFactory; + private RemoteSshdConfig _remoteSshdConfig; + + [TestInitialize] + public void SetUp() + { + _adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort); + _remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig(); + } + + [TestCleanup] + public void TearDown() + { + _remoteSshdConfig?.Reset(); + } + + [TestMethod] + [Owner("olegkap")] + [TestCategory("Cipher")] + public void Test_Cipher_TripleDESCBC_Connection() + { + _remoteSshdConfig.AddCipher(Cipher.TripledesCbc) + .Update() + .Restart(); + + var connectionInfo = new PasswordConnectionInfo(SshServerHostName, SshServerPort, User.UserName, User.Password); + connectionInfo.Encryptions.Clear(); + connectionInfo.Encryptions.Add("3des-cbc", new CipherInfo(192, (key, iv) => { return new TripleDesCipher(key, new CbcCipherMode(iv), null); })); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + client.Disconnect(); + } + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/PrivateKeyAuthenticationTests.cs b/src/Renci.SshNet.IntegrationTests/PrivateKeyAuthenticationTests.cs new file mode 100644 index 000000000..83313f4a8 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/PrivateKeyAuthenticationTests.cs @@ -0,0 +1,84 @@ +using Renci.SshNet.IntegrationTests.Common; +using Renci.SshNet.TestTools.OpenSSH; + +namespace Renci.SshNet.IntegrationTests +{ + [TestClass] + public class PrivateKeyAuthenticationTests : TestBase + { + private IConnectionInfoFactory _connectionInfoFactory; + private RemoteSshdConfig _remoteSshdConfig; + + [TestInitialize] + public void SetUp() + { + _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort); + _remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig(); + } + + [TestCleanup] + public void TearDown() + { + _remoteSshdConfig?.Reset(); + } + + [TestMethod] + public void Ecdsa256() + { + _remoteSshdConfig.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.EcdsaSha2Nistp256) + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod("key_ecdsa_256_openssh")); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + } + } + + [TestMethod] + public void Ecdsa384() + { + _remoteSshdConfig.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.EcdsaSha2Nistp384) + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod("key_ecdsa_384_openssh")); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + } + } + + [TestMethod] + public void EcdsaA521() + { + _remoteSshdConfig.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.EcdsaSha2Nistp521) + .Update() + .Restart(); + + var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod("key_ecdsa_521_openssh")); + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + } + } + + private PrivateKeyAuthenticationMethod CreatePrivateKeyAuthenticationMethod(string keyResource) + { + var privateKey = CreatePrivateKeyFromManifestResource("Renci.SshNet.IntegrationTests.resources.client." + keyResource); + return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKey); + } + + private PrivateKeyFile CreatePrivateKeyFromManifestResource(string resourceName) + { + using (var stream = GetManifestResourceStream(resourceName)) + { + return new PrivateKeyFile(stream); + } + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/Program.cs b/src/Renci.SshNet.IntegrationTests/Program.cs new file mode 100644 index 000000000..af2b60d51 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/Program.cs @@ -0,0 +1,11 @@ +namespace Renci.SshNet.IntegrationTests +{ + class Program + { +#if NETFRAMEWORK + private static void Main() + { + } +#endif + } +} diff --git a/src/Renci.SshNet.IntegrationTests/RemoteSshd.cs b/src/Renci.SshNet.IntegrationTests/RemoteSshd.cs new file mode 100644 index 000000000..c81bf96f5 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/RemoteSshd.cs @@ -0,0 +1,246 @@ +using Renci.SshNet.TestTools.OpenSSH; + +namespace Renci.SshNet.IntegrationTests +{ + internal class RemoteSshd + { + private readonly IConnectionInfoFactory _connectionInfoFactory; + + public RemoteSshd(IConnectionInfoFactory connectionInfoFactory) + { + _connectionInfoFactory = connectionInfoFactory; + } + + public RemoteSshdConfig OpenConfig() + { + return new RemoteSshdConfig(this, _connectionInfoFactory); + } + + public RemoteSshd Restart() + { + // Restart SSH daemon + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + // Kill all processes that start with 'sshd' and that run as root + var stopCommand = client.CreateCommand("sudo pkill -9 -U 0 sshd.pam"); + var stopOutput = stopCommand.Execute(); + if (stopCommand.ExitStatus != 0) + { + throw new ApplicationException($"Stopping ssh service failed with exit code {stopCommand.ExitStatus}.\r\n{stopOutput}\r\n{stopCommand.Error}"); + } + + var resetFailedCommand = client.CreateCommand("sudo /usr/sbin/sshd.pam"); + var resetFailedOutput = resetFailedCommand.Execute(); + if (resetFailedCommand.ExitStatus != 0) + { + throw new ApplicationException($"Reset failures for ssh service failed with exit code {resetFailedCommand.ExitStatus}.\r\n{resetFailedOutput}\r\n{resetFailedCommand.Error}"); + } + } + + return this; + } + } + + internal class RemoteSshdConfig + { + private const string SshdConfigFilePath = "/etc/ssh/sshd_config"; + private static readonly Encoding Utf8NoBom = new UTF8Encoding(false, true); + + private readonly RemoteSshd _remoteSshd; + private readonly IConnectionInfoFactory _connectionInfoFactory; + private readonly SshdConfig _config; + + public RemoteSshdConfig(RemoteSshd remoteSshd, IConnectionInfoFactory connectionInfoFactory) + { + _remoteSshd = remoteSshd; + _connectionInfoFactory = connectionInfoFactory; + + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var memoryStream = new MemoryStream()) + { + client.Download(SshdConfigFilePath, memoryStream); + + memoryStream.Position = 0; + _config = SshdConfig.LoadFrom(memoryStream, Encoding.UTF8); + } + } + } + + /// + /// Specifies whether challenge-response authentication is allowed. + /// + /// to allow challenge-response authentication. + /// + /// The current instance. + /// + public RemoteSshdConfig WithChallengeResponseAuthentication(bool? value) + { + _config.ChallengeResponseAuthentication = value; + return this; + } + + /// + /// Specifies whether to allow keyboard-interactive authentication. + /// + /// to allow keyboard-interactive authentication. + /// + /// The current instance. + /// + public RemoteSshdConfig WithKeyboardInteractiveAuthentication(bool value) + { + _config.KeyboardInteractiveAuthentication = value; + return this; + } + + /// + /// Specifies whether sshd should print /etc/motd when a user logs in interactively. + /// + /// if sshd should print /etc/motd when a user logs in interactively. + /// + /// The current instance. + /// + public RemoteSshdConfig PrintMotd(bool? value = true) + { + _config.PrintMotd = value; + return this; + } + + /// + /// Specifies whether TCP forwarding is permitted. + /// + /// to allow TCP forwarding. + /// + /// The current instance. + /// + public RemoteSshdConfig AllowTcpForwarding(bool? value = true) + { + _config.AllowTcpForwarding = value; + return this; + } + + public RemoteSshdConfig WithAuthenticationMethods(string user, string authenticationMethods) + { + var sshNetMatch = _config.Matches.FirstOrDefault(m => m.Users.Contains(user)); + if (sshNetMatch == null) + { + sshNetMatch = new Match(new[] { user }, new string[0]); + _config.Matches.Add(sshNetMatch); + } + + sshNetMatch.AuthenticationMethods = authenticationMethods; + + return this; + } + + public RemoteSshdConfig ClearCiphers() + { + _config.Ciphers.Clear(); + return this; + } + + public RemoteSshdConfig AddCipher(Cipher cipher) + { + _config.Ciphers.Add(cipher); + return this; + } + + public RemoteSshdConfig ClearKeyExchangeAlgorithms() + { + _config.KeyExchangeAlgorithms.Clear(); + return this; + } + + public RemoteSshdConfig AddKeyExchangeAlgorithm(KeyExchangeAlgorithm keyExchangeAlgorithm) + { + _config.KeyExchangeAlgorithms.Add(keyExchangeAlgorithm); + return this; + } + + public RemoteSshdConfig ClearPublicKeyAcceptedAlgorithms() + { + _config.PublicKeyAcceptedAlgorithms.Clear(); + return this; + } + + public RemoteSshdConfig AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm publicKeyAlgorithm) + { + _config.PublicKeyAcceptedAlgorithms.Add(publicKeyAlgorithm); + return this; + } + + public RemoteSshdConfig ClearHostKeyAlgorithms() + { + _config.HostKeyAlgorithms.Clear(); + return this; + } + + public RemoteSshdConfig AddHostKeyAlgorithm(HostKeyAlgorithm hostKeyAlgorithm) + { + _config.HostKeyAlgorithms.Add(hostKeyAlgorithm); + return this; + } + + public RemoteSshdConfig ClearSubsystems() + { + _config.Subsystems.Clear(); + return this; + } + + public RemoteSshdConfig AddSubsystem(Subsystem subsystem) + { + _config.Subsystems.Add(subsystem); + return this; + } + + public RemoteSshdConfig WithLogLevel(LogLevel logLevel) + { + _config.LogLevel = logLevel; + return this; + } + + public RemoteSshdConfig WithUsePAM(bool usePAM) + { + _config.UsePAM = usePAM; + return this; + } + + public RemoteSshdConfig ClearHostKeyFiles() + { + _config.HostKeyFiles.Clear(); + return this; + } + + public RemoteSshdConfig AddHostKeyFile(string hostKeyFile) + { + _config.HostKeyFiles.Add(hostKeyFile); + return this; + } + + public RemoteSshd Update() + { + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var memoryStream = new MemoryStream()) + using (var sw = new StreamWriter(memoryStream, Utf8NoBom)) + { + sw.NewLine = "\n"; + _config.SaveTo(sw); + sw.Flush(); + + memoryStream.Position = 0; + + client.Upload(memoryStream, SshdConfigFilePath); + } + } + + return _remoteSshd; + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/IntegrationTests.csproj b/src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj similarity index 65% rename from src/Renci.SshNet.IntegrationTests/IntegrationTests.csproj rename to src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj index 2fdbcfd74..ae3505f10 100644 --- a/src/Renci.SshNet.IntegrationTests/IntegrationTests.csproj +++ b/src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj @@ -3,7 +3,6 @@ net7.0 enable - enable false true @@ -16,16 +15,21 @@ We can stop producing XML docs for test projects (and remove the NoWarn for CS1591) once the following issue is fixed: https://github.com/dotnet/roslyn/issues/41640. --> - $(NoWarn);CS1591 + $(NoWarn);CS1591;SYSLIB0021;SYSLIB1045;SYSLIB0014;IDE0220;IDE0010 + + TRACE;FEATURE_MSTEST_DATATEST;FEATURE_SOCKET_EAP;FEATURE_ENCODING_ASCII;FEATURE_THREAD_SLEEP;FEATURE_THREAD_THREADPOOL + + - + + - + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -44,7 +48,18 @@ + - + + + + + + + + + + + diff --git a/src/Renci.SshNet.IntegrationTests/ScpClientTests.cs b/src/Renci.SshNet.IntegrationTests/ScpClientTests.cs index 5db1fe51c..fc90554a5 100644 --- a/src/Renci.SshNet.IntegrationTests/ScpClientTests.cs +++ b/src/Renci.SshNet.IntegrationTests/ScpClientTests.cs @@ -1,6 +1,4 @@ -using Renci.SshNet; - -namespace IntegrationTests +namespace Renci.SshNet.IntegrationTests { /// /// The SCP client integration tests diff --git a/src/Renci.SshNet.IntegrationTests/ScpTests.cs b/src/Renci.SshNet.IntegrationTests/ScpTests.cs new file mode 100644 index 000000000..2fd4ea0ce --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/ScpTests.cs @@ -0,0 +1,2379 @@ +using Renci.SshNet.Common; + +namespace Renci.SshNet.IntegrationTests +{ + // TODO SCP: UPLOAD / DOWNLOAD ZERO LENGTH FILES + // TODO SCP: UPLOAD / DOWNLOAD EMPTY DIRECTORY + // TODO SCP: UPLOAD DIRECTORY THAT ALREADY EXISTS ON REMOTE HOST + + [TestClass] + public class ScpTests : TestBase + { + private IConnectionInfoFactory _connectionInfoFactory; + private IRemotePathTransformation _remotePathTransformation; + + [TestInitialize] + public void SetUp() + { + _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort); + _remotePathTransformation = RemotePathTransformation.ShellQuote; + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpDownloadStreamDirectoryDoesNotExistData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Download_Stream_DirectoryDoesNotExist() + { + foreach (var data in GetScpDownloadStreamDirectoryDoesNotExistData()) + { + Scp_Download_Stream_DirectoryDoesNotExist((IRemotePathTransformation) data[0], + (string) data[1], + (string) data[2]); + } + } +#endif + public void Scp_Download_Stream_DirectoryDoesNotExist(IRemotePathTransformation remotePathTransformation, + string remotePath, + string remoteFile) + { + var completeRemotePath = CombinePaths(remotePath, remoteFile); + + // remove complete directory if it's not the home directory of the user + // or else remove the remote file + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + + try + { + using (var downloaded = new MemoryStream()) + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + try + { + client.Download(completeRemotePath, downloaded); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {completeRemotePath}: No such file or directory", ex.Message); + } + } + } + finally + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpDownloadStreamFileDoesNotExistData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Download_Stream_FileDoesNotExist() + { + foreach (var data in GetScpDownloadStreamFileDoesNotExistData()) + { + Scp_Download_Stream_FileDoesNotExist((IRemotePathTransformation)data[0], + (string)data[1], + (string)data[2]); + } + } +#endif + public void Scp_Download_Stream_FileDoesNotExist(IRemotePathTransformation remotePathTransformation, + string remotePath, + string remoteFile) + { + var completeRemotePath = CombinePaths(remotePath, remoteFile); + + // remove complete directory if it's not the home directory of the user + // or else remove the remote file + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + + client.CreateDirectory(remotePath); + } + } + + try + { + using (var downloaded = new MemoryStream()) + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + try + { + client.Download(completeRemotePath, downloaded); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {completeRemotePath}: No such file or directory", ex.Message); + } + } + } + finally + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpDownloadDirectoryInfoDirectoryDoesNotExistData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Download_DirectoryInfo_DirectoryDoesNotExist() + { + foreach (var data in GetScpDownloadDirectoryInfoDirectoryDoesNotExistData()) + { + Scp_Download_DirectoryInfo_DirectoryDoesNotExist((IRemotePathTransformation)data[0], + (string)data[1]); + } + } +#endif + public void Scp_Download_DirectoryInfo_DirectoryDoesNotExist(IRemotePathTransformation remotePathTransformation, + string remotePath) + { + var localDirectory = Path.GetTempFileName(); + File.Delete(localDirectory); + Directory.CreateDirectory(localDirectory); + + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + try + { + client.Download(remotePath, new DirectoryInfo(localDirectory)); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {remotePath}: No such file or directory", ex.Message); + } + } + } + finally + { + Directory.Delete(localDirectory, true); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpDownloadDirectoryInfoExistingFileData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Download_DirectoryInfo_ExistingFile() + { + foreach (var data in GetScpDownloadDirectoryInfoExistingFileData()) + { + Scp_Download_DirectoryInfo_ExistingFile((IRemotePathTransformation)data[0], + (string)data[1]); + } + } +#endif + public void Scp_Download_DirectoryInfo_ExistingFile(IRemotePathTransformation remotePathTransformation, + string remotePath) + { + var content = CreateMemoryStream(100); + content.Position = 0; + + var localDirectory = Path.GetTempFileName(); + File.Delete(localDirectory); + Directory.CreateDirectory(localDirectory); + + var localFile = Path.Combine(localDirectory, PosixPath.GetFileName(remotePath)); + + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.UploadFile(content, remotePath); + } + + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + client.Download(remotePath, new DirectoryInfo(localDirectory)); + } + + Assert.IsTrue(File.Exists(localFile)); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remotePath, downloaded); + downloaded.Position = 0; + Assert.AreEqual(CreateFileHash(localFile), CreateHash(downloaded)); + } + } + } + finally + { + Directory.Delete(localDirectory, true); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remotePath)) + { + client.DeleteFile(remotePath); + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpDownloadDirectoryInfoExistingDirectoryData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Download_DirectoryInfo_ExistingDirectory() + { + foreach (var data in GetScpDownloadDirectoryInfoExistingDirectoryData()) + { + Scp_Download_DirectoryInfo_ExistingDirectory((IRemotePathTransformation)data[0], + (string)data[1]); + } + } +#endif + public void Scp_Download_DirectoryInfo_ExistingDirectory(IRemotePathTransformation remotePathTransformation, + string remotePath) + { + var localDirectory = Path.GetTempFileName(); + File.Delete(localDirectory); + Directory.CreateDirectory(localDirectory); + + var localPathFile1 = Path.Combine(localDirectory, "file1 23"); + var remotePathFile1 = CombinePaths(remotePath, "file1 23"); + var contentFile1 = CreateMemoryStream(1024); + contentFile1.Position = 0; + + var localPathFile2 = Path.Combine(localDirectory, "file2 #$%"); + var remotePathFile2 = CombinePaths(remotePath, "file2 #$%"); + var contentFile2 = CreateMemoryStream(2048); + contentFile2.Position = 0; + + var localPathSubDirectory = Path.Combine(localDirectory, "subdir $1%#"); + var remotePathSubDirectory = CombinePaths(remotePath, "subdir $1%#"); + + var localPathFile3 = Path.Combine(localPathSubDirectory, "file3 %$#"); + var remotePathFile3 = CombinePaths(remotePathSubDirectory, "file3 %$#"); + var contentFile3 = CreateMemoryStream(256); + contentFile3.Position = 0; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remotePathFile1)) + { + client.DeleteFile(remotePathFile1); + } + + if (client.Exists(remotePathFile2)) + { + client.DeleteFile(remotePathFile2); + } + + if (client.Exists(remotePathFile3)) + { + client.DeleteFile(remotePathFile3); + } + + if (client.Exists(remotePathSubDirectory)) + { + client.DeleteDirectory(remotePathSubDirectory); + } + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (!client.Exists(remotePath)) + { + client.CreateDirectory(remotePath); + } + + client.UploadFile(contentFile1, remotePathFile1); + client.UploadFile(contentFile1, remotePathFile2); + + client.CreateDirectory(remotePathSubDirectory); + client.UploadFile(contentFile3, remotePathFile3); + } + + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + client.Download(remotePath, new DirectoryInfo(localDirectory)); + } + + var localFiles = Directory.GetFiles(localDirectory); + Assert.AreEqual(2, localFiles.Length); + Assert.IsTrue(localFiles.Contains(localPathFile1)); + Assert.IsTrue(localFiles.Contains(localPathFile2)); + + var localSubDirecties = Directory.GetDirectories(localDirectory); + Assert.AreEqual(1, localSubDirecties.Length); + Assert.AreEqual(localPathSubDirectory, localSubDirecties[0]); + + var localFilesSubDirectory = Directory.GetFiles(localPathSubDirectory); + Assert.AreEqual(1, localFilesSubDirectory.Length); + Assert.AreEqual(localPathFile3, localFilesSubDirectory[0]); + + Assert.AreEqual(0, Directory.GetDirectories(localPathSubDirectory).Length); + } + finally + { + Directory.Delete(localDirectory, true); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remotePathFile1)) + { + client.DeleteFile(remotePathFile1); + } + + if (client.Exists(remotePathFile2)) + { + client.DeleteFile(remotePathFile2); + } + + if (client.Exists(remotePathFile3)) + { + client.DeleteFile(remotePathFile3); + } + + if (client.Exists(remotePathSubDirectory)) + { + client.DeleteDirectory(remotePathSubDirectory); + } + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpDownloadFileInfoDirectoryDoesNotExistData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Download_FileInfo_DirectoryDoesNotExist() + { + foreach (var data in GetScpDownloadFileInfoDirectoryDoesNotExistData()) + { + Scp_Download_FileInfo_DirectoryDoesNotExist((IRemotePathTransformation)data[0], + (string)data[1], + (string)data[2]); + } + } +#endif + public void Scp_Download_FileInfo_DirectoryDoesNotExist(IRemotePathTransformation remotePathTransformation, + string remotePath, + string remoteFile) + { + var completeRemotePath = CombinePaths(remotePath, remoteFile); + + // remove complete directory if it's not the home directory of the user + // or else remove the remote file + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + + var fileInfo = new FileInfo(Path.GetTempFileName()); + + try + { + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + try + { + client.Download(completeRemotePath, fileInfo); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {completeRemotePath}: No such file or directory", ex.Message); + } + } + } + finally + { + fileInfo.Delete(); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpDownloadFileInfoFileDoesNotExistData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Download_FileInfo_FileDoesNotExist() + { + foreach (var data in GetScpDownloadFileInfoFileDoesNotExistData()) + { + Scp_Download_FileInfo_FileDoesNotExist((IRemotePathTransformation)data[0], + (string)data[1], + (string)data[2]); + } + } +#endif + public void Scp_Download_FileInfo_FileDoesNotExist(IRemotePathTransformation remotePathTransformation, + string remotePath, + string remoteFile) + { + var completeRemotePath = CombinePaths(remotePath, remoteFile); + + // remove complete directory if it's not the home directory of the user + // or else remove the remote file + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + + client.CreateDirectory(remotePath); + } + } + + var fileInfo = new FileInfo(Path.GetTempFileName()); + + try + { + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + try + { + client.Download(completeRemotePath, fileInfo); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {completeRemotePath}: No such file or directory", ex.Message); + } + } + } + finally + { + fileInfo.Delete(); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpDownloadFileInfoExistingDirectoryData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Download_FileInfo_ExistingDirectory() + { + foreach (var data in GetScpDownloadFileInfoExistingDirectoryData()) + { + Scp_Download_FileInfo_ExistingDirectory((IRemotePathTransformation)data[0], + (string)data[1]); + } + } +#endif + public void Scp_Download_FileInfo_ExistingDirectory(IRemotePathTransformation remotePathTransformation, + string remotePath) + { + // remove complete directory if it's not the home directory of the user + // or else remove the remote file + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + + client.CreateDirectory(remotePath); + } + } + + var fileInfo = new FileInfo(Path.GetTempFileName()); + fileInfo.Delete(); + + try + { + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + try + { + client.Download(remotePath, fileInfo); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {remotePath}: not a regular file", ex.Message); + } + + Assert.IsFalse(fileInfo.Exists); + } + } + finally + { + fileInfo.Delete(); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpDownloadFileInfoExistingFileData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Download_FileInfo_ExistingFile() + { + foreach (var data in GetScpDownloadFileInfoExistingFileData()) + { + Scp_Download_FileInfo_ExistingFile((IRemotePathTransformation)data[0], + (string)data[1], + (string)data[2], + (int)data[3]); + } + } +#endif + public void Scp_Download_FileInfo_ExistingFile(IRemotePathTransformation remotePathTransformation, + string remotePath, + string remoteFile, + int size) + { + var completeRemotePath = CombinePaths(remotePath, remoteFile); + + // remove complete directory if it's not the home directory of the user + // or else remove the remote file + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + + client.CreateDirectory(remotePath); + } + } + + var fileInfo = new FileInfo(Path.GetTempFileName()); + + try + { + var content = CreateMemoryStream(size); + content.Position = 0; + + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + client.Upload(content, completeRemotePath); + } + + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + client.Download(completeRemotePath, fileInfo); + } + + using (var fs = fileInfo.OpenRead()) + { + var downloadedBytes = new byte[fs.Length]; + Assert.AreEqual(downloadedBytes.Length, fs.Read(downloadedBytes, 0, downloadedBytes.Length)); + content.Position = 0; + Assert.AreEqual(CreateHash(content), CreateHash(downloadedBytes)); + } + } + finally + { + fileInfo.Delete(); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpDownloadStreamExistingDirectoryData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Download_Stream_ExistingDirectory() + { + foreach (var data in GetScpDownloadStreamExistingDirectoryData()) + { + Scp_Download_Stream_ExistingDirectory((IRemotePathTransformation)data[0], + (string)data[1]); + } + } +#endif + public void Scp_Download_Stream_ExistingDirectory(IRemotePathTransformation remotePathTransformation, + string remotePath) + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + + client.CreateDirectory(remotePath); + } + } + + var file = Path.GetTempFileName(); + File.Delete(file); + + try + { + using (var fs = File.OpenWrite(file)) + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + try + { + client.Download(remotePath, fs); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {remotePath}: not a regular file", ex.Message); + } + + Assert.AreEqual(0, fs.Length); + } + } + finally + { + File.Delete(file); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpDownloadStreamExistingFileData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Download_Stream_ExistingFile() + { + foreach (var data in GetScpDownloadStreamExistingFileData()) + { + Scp_Download_Stream_ExistingFile((IRemotePathTransformation)data[0], + (string)data[1], + (string)data[2], + (int)data[3]); + } + } +#endif + public void Scp_Download_Stream_ExistingFile(IRemotePathTransformation remotePathTransformation, + string remotePath, + string remoteFile, + int size) + { + var completeRemotePath = CombinePaths(remotePath, remoteFile); + + // remove complete directory if it's not the home directory of the user + // or else remove the remote file + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + + client.CreateDirectory(remotePath); + } + } + + var file = CreateTempFile(size); + + try + { + using (var fs = File.OpenRead(file)) + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + client.Upload(fs, completeRemotePath); + } + + using (var fs = File.OpenRead(file)) + using (var downloaded = new MemoryStream(size)) + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + client.Download(completeRemotePath, downloaded); + downloaded.Position = 0; + Assert.AreEqual(CreateHash(fs), CreateHash(downloaded)); + } + } + finally + { + File.Delete(file); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpUploadFileStreamDirectoryDoesNotExistData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Upload_FileStream_DirectoryDoesNotExist() + { + foreach (var data in GetScpUploadFileStreamDirectoryDoesNotExistData()) + { + Scp_Upload_FileStream_DirectoryDoesNotExist((IRemotePathTransformation)data[0], + (string)data[1], + (string)data[2]); + } + } +#endif + public void Scp_Upload_FileStream_DirectoryDoesNotExist(IRemotePathTransformation remotePathTransformation, + string remotePath, + string remoteFile) + { + var completeRemotePath = CombinePaths(remotePath, remoteFile); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + + var file = CreateTempFile(1000); + + try + { + using (var fs = File.OpenRead(file)) + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + try + { + client.Upload(fs, completeRemotePath); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {remotePath}: No such file or directory", ex.Message); + } + } + } + finally + { + File.Delete(file); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpUploadFileStreamExistingDirectoryData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Upload_FileStream_ExistingDirectory() + { + foreach (var data in GetScpUploadFileStreamExistingDirectoryData()) + { + Scp_Upload_FileStream_ExistingDirectory((IRemotePathTransformation)data[0], + (string)data[1]); + } + } +#endif + public void Scp_Upload_FileStream_ExistingDirectory(IRemotePathTransformation remotePathTransformation, + string remoteFile) + { + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteFile))) + { + command.Execute(); + } + } + + var file = CreateTempFile(1000); + + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.CreateDirectory(remoteFile); + } + + using (var fs = File.OpenRead(file)) + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + try + { + client.Upload(fs, remoteFile); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {remoteFile}: Is a directory", ex.Message); + } + } + } + finally + { + File.Delete(file); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteDirectory(remoteFile); + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(ScpUploadFileStreamExistingFileData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Upload_FileStream_ExistingFile() + { + foreach (var data in ScpUploadFileStreamExistingFileData()) + { + Scp_Upload_FileStream_ExistingFile((IRemotePathTransformation)data[0], + (string)data[1]); + } + } +#endif + public void Scp_Upload_FileStream_ExistingFile(IRemotePathTransformation remotePathTransformation, + string remoteFile) + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + + // original content is bigger than new content to ensure file is fully overwritten + var originalContent = CreateMemoryStream(2000); + var file = CreateTempFile(1000); + + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + originalContent.Position = 0; + client.UploadFile(originalContent, remoteFile); + } + + using (var fs = File.OpenRead(file)) + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + client.Upload(fs, remoteFile); + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var downloaded = new MemoryStream(1000)) + { + client.DownloadFile(remoteFile, downloaded); + downloaded.Position = 0; + Assert.AreEqual(CreateFileHash(file), CreateHash(downloaded)); + } + } + } + finally + { + File.Delete(file); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpUploadFileStreamFileDoesNotExistData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Upload_FileStream_FileDoesNotExist() + { + foreach (var data in GetScpUploadFileStreamFileDoesNotExistData()) + { + Scp_Upload_FileStream_FileDoesNotExist((IRemotePathTransformation)data[0], + (string)data[1], + (string)data[2], + (int)data[3]); + } + } +#endif + public void Scp_Upload_FileStream_FileDoesNotExist(IRemotePathTransformation remotePathTransformation, + string remotePath, + string remoteFile, + int size) + { + var completeRemotePath = CombinePaths(remotePath, remoteFile); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + // remove complete directory if it's not the home directory of the user + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + + var file = CreateTempFile(size); + + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + // create directory if it's not the home directory of the user + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (!client.Exists((remotePath))) + { + client.CreateDirectory(remotePath); + } + } + } + + using (var fs = File.OpenRead(file)) + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + client.Upload(fs, completeRemotePath); + } + + using (var fs = File.OpenRead(file)) + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var sftpFile = client.Get(completeRemotePath); + Assert.AreEqual(GetAbsoluteRemotePath(client, remotePath, remoteFile), sftpFile.FullName); + Assert.AreEqual(size, sftpFile.Length); + + var downloaded = client.ReadAllBytes(completeRemotePath); + Assert.AreEqual(CreateHash(fs), CreateHash(downloaded)); + } + } + finally + { + File.Delete(file); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + // remove complete directory if it's not the home directory of the user + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + } + + /// + /// https://github.com/sshnet/SSH.NET/issues/289 + /// +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpUploadFileInfoDirectoryDoesNotExistData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Upload_FileInfo_DirectoryDoesNotExist() + { + foreach (var data in GetScpUploadFileInfoDirectoryDoesNotExistData()) + { + Scp_Upload_FileInfo_DirectoryDoesNotExist((IRemotePathTransformation)data[0], + (string)data[1], + (string)data[2]); + } + } +#endif + public void Scp_Upload_FileInfo_DirectoryDoesNotExist(IRemotePathTransformation remotePathTransformation, + string remotePath, + string remoteFile) + { + var completeRemotePath = CombinePaths(remotePath, remoteFile); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + + var file = CreateTempFile(1000); + + try + { + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + try + { + client.Upload(new FileInfo(file), completeRemotePath); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {remotePath}: No such file or directory", ex.Message); + } + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + Assert.IsFalse(client.Exists(completeRemotePath)); + Assert.IsFalse(client.Exists(remotePath)); + } + } + finally + { + File.Delete(file); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + + /// + /// https://github.com/sshnet/SSH.NET/issues/286 + /// +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpUploadFileInfoExistingDirectoryData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Upload_FileInfo_ExistingDirectory() + { + foreach (var data in GetScpUploadFileInfoExistingDirectoryData()) + { + Scp_Upload_FileInfo_ExistingDirectory((IRemotePathTransformation)data[0], + (string)data[1]); + } + } +#endif + public void Scp_Upload_FileInfo_ExistingDirectory(IRemotePathTransformation remotePathTransformation, + string remoteFile) + { + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteFile))) + { + command.Execute(); + } + } + + var file = CreateTempFile(1000); + + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.CreateDirectory(remoteFile); + } + + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + try + { + client.Upload(new FileInfo(file), remoteFile); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {remoteFile}: Is a directory", ex.Message); + } + } + } + finally + { + File.Delete(file); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteFile))) + { + command.Execute(); + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpUploadFileInfoExistingFileData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Upload_FileInfo_ExistingFile() + { + foreach (var data in GetScpUploadFileInfoExistingFileData()) + { + Scp_Upload_FileInfo_ExistingFile((IRemotePathTransformation)data[0], + (string)data[1]); + } + } +#endif + public void Scp_Upload_FileInfo_ExistingFile(IRemotePathTransformation remotePathTransformation, + string remoteFile) + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + + // original content is bigger than new content to ensure file is fully overwritten + var originalContent = CreateMemoryStream(2000); + var file = CreateTempFile(1000); + + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + originalContent.Position = 0; + client.UploadFile(originalContent, remoteFile); + } + + var fileInfo = new FileInfo(file) + { + LastAccessTimeUtc = new DateTime(1973, 8, 13, 20, 15, 33, DateTimeKind.Utc), + LastWriteTimeUtc = new DateTime(1974, 1, 24, 3, 55, 12, DateTimeKind.Utc) + }; + + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + client.Upload(fileInfo, remoteFile); + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var uploadedFile = client.Get(remoteFile); + Assert.AreEqual(fileInfo.LastAccessTimeUtc, uploadedFile.LastAccessTimeUtc); + Assert.AreEqual(fileInfo.LastWriteTimeUtc, uploadedFile.LastWriteTimeUtc); + + using (var downloaded = new MemoryStream(1000)) + { + client.DownloadFile(remoteFile, downloaded); + downloaded.Position = 0; + Assert.AreEqual(CreateFileHash(file), CreateHash(downloaded)); + } + } + } + finally + { + File.Delete(file); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpUploadFileInfoFileDoesNotExistData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Upload_FileInfo_FileDoesNotExist() + { + foreach (var data in GetScpUploadFileInfoFileDoesNotExistData()) + { + Scp_Upload_FileInfo_FileDoesNotExist((IRemotePathTransformation)data[0], + (string)data[1], + (string)data[2], + (int)data[3]); + } + } +#endif + public void Scp_Upload_FileInfo_FileDoesNotExist(IRemotePathTransformation remotePathTransformation, + string remotePath, + string remoteFile, + int size) + { + var completeRemotePath = CombinePaths(remotePath, remoteFile); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.DeleteFile(completeRemotePath); + } + + // remove complete directory if it's not the home directory of the user + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + + var file = CreateTempFile(size); + + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + // create directory if it's not the home directory of the user + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (!client.Exists(remotePath)) + { + client.CreateDirectory(remotePath); + } + } + } + + var fileInfo = new FileInfo(file) + { + LastAccessTimeUtc = new DateTime(1973, 8, 13, 20, 15, 33, DateTimeKind.Utc), + LastWriteTimeUtc = new DateTime(1974, 1, 24, 3, 55, 12, DateTimeKind.Utc) + }; + + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + client.Upload(fileInfo, completeRemotePath); + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var uploadedFile = client.Get(completeRemotePath); + Assert.AreEqual(fileInfo.LastAccessTimeUtc, uploadedFile.LastAccessTimeUtc); + Assert.AreEqual(fileInfo.LastWriteTimeUtc, uploadedFile.LastWriteTimeUtc); + Assert.AreEqual(size, uploadedFile.Length); + + using (var downloaded = new MemoryStream(size)) + { + client.DownloadFile(completeRemotePath, downloaded); + downloaded.Position = 0; + Assert.AreEqual(CreateFileHash(file), CreateHash(downloaded)); + } + } + } + finally + { + File.Delete(file); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(completeRemotePath)) + { + client.Delete(completeRemotePath); + } + + // remove complete directory if it's not the home directory of the user + if (remotePath.Length > 0 && remotePath != client.WorkingDirectory) + { + if (client.Exists(remotePath)) + { + client.DeleteDirectory(remotePath); + } + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpUploadDirectoryInfoDirectoryDoesNotExistData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Upload_DirectoryInfo_DirectoryDoesNotExist() + { + foreach (var data in GetScpUploadDirectoryInfoDirectoryDoesNotExistData()) + { + Scp_Upload_DirectoryInfo_DirectoryDoesNotExist((IRemotePathTransformation)data[0], + (string)data[1]); + } + } +#endif + public void Scp_Upload_DirectoryInfo_DirectoryDoesNotExist(IRemotePathTransformation remotePathTransformation, + string remoteDirectory) + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists((remoteDirectory))) + { + client.DeleteDirectory(remoteDirectory); + } + } + + var localDirectory = Path.GetTempFileName(); + File.Delete(localDirectory); + Directory.CreateDirectory(localDirectory); + + try + { + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + try + { + client.Upload(new DirectoryInfo(localDirectory), remoteDirectory); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {remoteDirectory}: No such file or directory", ex.Message); + } + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + Assert.IsFalse(client.Exists(remoteDirectory)); + } + } + finally + { + Directory.Delete(localDirectory, true); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists((remoteDirectory))) + { + client.DeleteDirectory(remoteDirectory); + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpUploadDirectoryInfoExistingDirectoryData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Upload_DirectoryInfo_ExistingDirectory() + { + foreach (var data in GetScpUploadDirectoryInfoExistingDirectoryData()) + { + Scp_Upload_DirectoryInfo_ExistingDirectory((IRemotePathTransformation)data[0], + (string)data[1]); + } + } +#endif + public void Scp_Upload_DirectoryInfo_ExistingDirectory(IRemotePathTransformation remotePathTransformation, + string remoteDirectory) + { + string absoluteRemoteDirectory = GetAbsoluteRemotePath(_connectionInfoFactory, remoteDirectory); + + var remotePathFile1 = CombinePaths(remoteDirectory, "file1"); + var remotePathFile2 = CombinePaths(remoteDirectory, "file2"); + + var absoluteremoteSubDirectory1 = CombinePaths(absoluteRemoteDirectory, "sub1"); + var remoteSubDirectory1 = CombinePaths(remoteDirectory, "sub1"); + var remotePathSubFile1 = CombinePaths(remoteSubDirectory1, "file1"); + var remotePathSubFile2 = CombinePaths(remoteSubDirectory1, "file2"); + var absoluteremoteSubDirectory2 = CombinePaths(absoluteRemoteDirectory, "sub2"); + var remoteSubDirectory2 = CombinePaths(remoteDirectory, "sub2"); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remotePathSubFile1)) + { + client.DeleteFile(remotePathSubFile1); + } + if (client.Exists(remotePathSubFile2)) + { + client.DeleteFile(remotePathSubFile2); + } + if (client.Exists(remoteSubDirectory1)) + { + client.DeleteDirectory(remoteSubDirectory1); + } + if (client.Exists(remoteSubDirectory2)) + { + client.DeleteDirectory(remoteSubDirectory2); + } + if (client.Exists(remotePathFile1)) + { + client.DeleteFile(remotePathFile1); + } + if (client.Exists(remotePathFile2)) + { + client.DeleteFile(remotePathFile2); + } + + if (remoteDirectory.Length > 0 && remoteDirectory != "." && remoteDirectory != client.WorkingDirectory) + { + if (client.Exists(remoteDirectory)) + { + client.DeleteDirectory(remoteDirectory); + } + + client.CreateDirectory(remoteDirectory); + } + } + + var localDirectory = Path.GetTempFileName(); + File.Delete(localDirectory); + Directory.CreateDirectory(localDirectory); + + var localPathFile1 = Path.Combine(localDirectory, "file1"); + var localPathFile2 = Path.Combine(localDirectory, "file2"); + + var localSubDirectory1 = Path.Combine(localDirectory, "sub1"); + var localPathSubFile1 = Path.Combine(localSubDirectory1, "file1"); + var localPathSubFile2 = Path.Combine(localSubDirectory1, "file2"); + var localSubDirectory2 = Path.Combine(localDirectory, "sub2"); + + try + { + CreateFile(localPathFile1, 2000); + File.SetLastWriteTimeUtc(localPathFile1, new DateTime(2015, 8, 24, 5, 32, 16, DateTimeKind.Utc)); + + CreateFile(localPathFile2, 1000); + File.SetLastWriteTimeUtc(localPathFile2, new DateTime(2012, 3, 27, 23, 2, 54, DateTimeKind.Utc)); + + // create subdirectory with two files + Directory.CreateDirectory(localSubDirectory1); + CreateFile(localPathSubFile1, 1000); + File.SetLastWriteTimeUtc(localPathSubFile1, new DateTime(2013, 4, 12, 16, 54, 22, DateTimeKind.Utc)); + CreateFile(localPathSubFile2, 2000); + File.SetLastWriteTimeUtc(localPathSubFile2, new DateTime(2015, 8, 4, 12, 43, 12, DateTimeKind.Utc)); + Directory.SetLastWriteTimeUtc(localSubDirectory1, + new DateTime(2014, 6, 12, 13, 2, 44, DateTimeKind.Utc)); + + // create empty subdirectory + Directory.CreateDirectory(localSubDirectory2); + Directory.SetLastWriteTimeUtc(localSubDirectory2, + new DateTime(2011, 5, 14, 1, 5, 12, DateTimeKind.Utc)); + + Directory.SetLastWriteTimeUtc(localDirectory, new DateTime(2015, 10, 14, 22, 45, 11, DateTimeKind.Utc)); + + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + client.Upload(new DirectoryInfo(localDirectory), remoteDirectory); + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + Assert.IsTrue(client.Exists(remoteDirectory)); + + var remoteSftpDirectory = client.Get(remoteDirectory); + Assert.IsNotNull(remoteSftpDirectory); + Assert.AreEqual(absoluteRemoteDirectory, remoteSftpDirectory.FullName); + Assert.IsTrue(remoteSftpDirectory.IsDirectory); + Assert.IsFalse(remoteSftpDirectory.IsRegularFile); + + // Due to CVE-2018-20685, we can no longer set the times or modes on a file or directory + // that refers to the current directory ('.'), the parent directory ('..') or a directory + // containing a forward slash ('/'). + Assert.AreNotEqual(Directory.GetLastWriteTimeUtc(localDirectory), remoteSftpDirectory.LastWriteTimeUtc); + + Assert.IsTrue(client.Exists(remotePathFile1)); + Assert.AreEqual(CreateFileHash(localPathFile1), CreateRemoteFileHash(client, remotePathFile1)); + var remoteSftpFile = client.Get(remotePathFile1); + Assert.IsNotNull(remoteSftpFile); + Assert.IsFalse(remoteSftpFile.IsDirectory); + Assert.IsTrue(remoteSftpFile.IsRegularFile); + Assert.AreEqual(File.GetLastWriteTimeUtc(localPathFile1), remoteSftpFile.LastWriteTimeUtc); + + Assert.IsTrue(client.Exists(remotePathFile2)); + Assert.AreEqual(CreateFileHash(localPathFile2), CreateRemoteFileHash(client, remotePathFile2)); + remoteSftpFile = client.Get(remotePathFile2); + Assert.IsNotNull(remoteSftpFile); + Assert.IsFalse(remoteSftpFile.IsDirectory); + Assert.IsTrue(remoteSftpFile.IsRegularFile); + Assert.AreEqual(File.GetLastWriteTimeUtc(localPathFile2), remoteSftpFile.LastWriteTimeUtc); + + Assert.IsTrue(client.Exists(remoteSubDirectory1)); + remoteSftpDirectory = client.Get(remoteSubDirectory1); + Assert.IsNotNull(remoteSftpDirectory); + Assert.AreEqual(absoluteremoteSubDirectory1, remoteSftpDirectory.FullName); + Assert.IsTrue(remoteSftpDirectory.IsDirectory); + Assert.IsFalse(remoteSftpDirectory.IsRegularFile); + Assert.AreEqual(Directory.GetLastWriteTimeUtc(localSubDirectory1), remoteSftpDirectory.LastWriteTimeUtc); + + Assert.IsTrue(client.Exists(remotePathSubFile1)); + Assert.AreEqual(CreateFileHash(localPathSubFile1), CreateRemoteFileHash(client, remotePathSubFile1)); + + Assert.IsTrue(client.Exists(remotePathSubFile2)); + Assert.AreEqual(CreateFileHash(localPathSubFile2), CreateRemoteFileHash(client, remotePathSubFile2)); + + Assert.IsTrue(client.Exists(remoteSubDirectory2)); + remoteSftpDirectory = client.Get(remoteSubDirectory2); + Assert.IsNotNull(remoteSftpDirectory); + Assert.AreEqual(absoluteremoteSubDirectory2, remoteSftpDirectory.FullName); + Assert.IsTrue(remoteSftpDirectory.IsDirectory); + Assert.IsFalse(remoteSftpDirectory.IsRegularFile); + Assert.AreEqual(Directory.GetLastWriteTimeUtc(localSubDirectory2), remoteSftpDirectory.LastWriteTimeUtc); + } + } + finally + { + Directory.Delete(localDirectory, true); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remotePathSubFile1)) + { + client.DeleteFile(remotePathSubFile1); + } + if (client.Exists(remotePathSubFile2)) + { + client.DeleteFile(remotePathSubFile2); + } + if (client.Exists(remoteSubDirectory1)) + { + client.DeleteDirectory(remoteSubDirectory1); + } + if (client.Exists(remoteSubDirectory2)) + { + client.DeleteDirectory(remoteSubDirectory2); + } + if (client.Exists(remotePathFile1)) + { + client.DeleteFile(remotePathFile1); + } + if (client.Exists(remotePathFile2)) + { + client.DeleteFile(remotePathFile2); + } + + if (remoteDirectory.Length > 0 && remoteDirectory != "." && remoteDirectory != client.WorkingDirectory) + { + if (client.Exists(remoteDirectory)) + { + client.DeleteDirectory(remoteDirectory); + } + } + } + } + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetScpUploadDirectoryInfoExistingFileData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Scp_Upload_DirectoryInfo_ExistingFile() + { + foreach (var data in GetScpUploadDirectoryInfoExistingFileData()) + { + Scp_Upload_DirectoryInfo_ExistingFile((IRemotePathTransformation)data[0], + (string)data[1]); + } + } +#endif + public void Scp_Upload_DirectoryInfo_ExistingFile(IRemotePathTransformation remotePathTransformation, + string remoteDirectory) + { + var remotePathFile1 = CombinePaths(remoteDirectory, "file1"); + var remotePathFile2 = CombinePaths(remoteDirectory, "file2"); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + Console.WriteLine(client.ConnectionInfo.CurrentKeyExchangeAlgorithm); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory))) + { + command.Execute(); + } + } + + var localDirectory = Path.GetTempFileName(); + File.Delete(localDirectory); + Directory.CreateDirectory(localDirectory); + + var localPathFile1 = Path.Combine(localDirectory, "file1"); + var localPathFile2 = Path.Combine(localDirectory, "file2"); + + try + { + CreateFile(localPathFile1, 50); + CreateFile(localPathFile2, 50); + + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + if (remotePathTransformation != null) + { + client.RemotePathTransformation = remotePathTransformation; + } + + client.Connect(); + + CreateRemoteFile(client, remoteDirectory, 10); + + try + { + client.Upload(new DirectoryInfo(localDirectory), remoteDirectory); + Assert.Fail(); + } + catch (ScpException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual($"scp: {remoteDirectory}: Not a directory", ex.Message); + } + } + } + finally + { + Directory.Delete(localDirectory, true); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remotePathFile1)) + { + client.DeleteFile(remotePathFile1); + } + if (client.Exists(remotePathFile2)) + { + client.DeleteFile(remotePathFile2); + } + if (client.Exists((remoteDirectory))) + { + client.DeleteFile(remoteDirectory); + } + } + } + } + + private static IEnumerable GetScpDownloadStreamDirectoryDoesNotExistData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-directorydoesnotexist", "scp-file" }; + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-directorydoesnotexist", "scp-file" }; + } + + private static IEnumerable GetScpUploadFileInfoFileDoesNotExistData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet", "test123", 0 }; + yield return new object[] { RemotePathTransformation.None, "/home/sshnet", "test123", 5 * 1024 * 1024 }; + yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'sp\u0100ce \\tab\tlf\n*?[#~=%", "file123", 1024 }; + yield return new object[] { null, "/home/sshnet/scp test", "file 123", 1024 }; + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-test", "file|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%", 1024 }; + yield return new object[] { null, "", "scp-issue280", 1024 }; + } + + private static IEnumerable GetScpUploadFileStreamFileDoesNotExistData() + { + yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'sp\u0100ce \\tab\tlf\n*?[#~=%", "file123", 0 }; + yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'sp\u0100ce \\tab\tlf\n*?[#~=%", "file123", 1024 }; + yield return new object[] { null, "/home/sshnet/scp test", "file 123", 1024 }; + yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/scp-test", "file|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%", 1024 }; + yield return new object[] { RemotePathTransformation.None, "", "scp-issue280", 1024 }; + } + + private static IEnumerable GetScpUploadDirectoryInfoExistingDirectoryData() + { + yield return new object[] { RemotePathTransformation.None, "scp-directorydoesnotexist" }; + yield return new object[] { RemotePathTransformation.None, "." }; + yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%" }; + } + + private static IEnumerable GetScpUploadDirectoryInfoExistingFileData() + { + yield return new object[] { RemotePathTransformation.None, "scp-upload-file" }; + } + + private static IEnumerable ScpUploadFileStreamExistingFileData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-upload-file" }; + } + + private static IEnumerable GetScpDownloadStreamFileDoesNotExistData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet", "scp-filedoesnotexist" }; + } + + private static IEnumerable GetScpDownloadDirectoryInfoDirectoryDoesNotExistData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-download" }; + } + + private static IEnumerable GetScpDownloadDirectoryInfoExistingFileData() + { + yield return new object[] { RemotePathTransformation.None, "scp-download" }; + } + + private static IEnumerable GetScpDownloadDirectoryInfoExistingDirectoryData() + { + yield return new object[] { RemotePathTransformation.None, "scp-download" }; + yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'space \\tab\tlf*?[#~=%" }; + } + + private static IEnumerable GetScpDownloadFileInfoDirectoryDoesNotExistData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-directorydoesnotexist", "scp-file" }; + } + + private static IEnumerable GetScpDownloadFileInfoFileDoesNotExistData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet", "scp-filedoesnotexist" }; + } + + private static IEnumerable GetScpDownloadFileInfoExistingDirectoryData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-test" }; + } + + private static IEnumerable GetScpDownloadFileInfoExistingFileData() + { + yield return new object[] { null, "", "file 123", 0 }; + yield return new object[] { null, "", "file 123", 1024 }; + yield return new object[] { RemotePathTransformation.ShellQuote, "", "file|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%", 1024 }; + yield return new object[] { null, "/home/sshnet/scp test", "file 123", 1024 }; + yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'sp\u0100ce \\tab\tlf\n*?[#~=%", "file123", 1024 }; + yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/scp-test", "file|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%", 1024 }; + } + + private static IEnumerable GetScpDownloadStreamExistingDirectoryData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-test" }; + } + + private static IEnumerable GetScpDownloadStreamExistingFileData() + { + yield return new object[] { null, "", "file 123", 0 }; + yield return new object[] { null, "", "file 123", 1024 }; + yield return new object[] { RemotePathTransformation.ShellQuote, "", "file|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%", 1024 }; + yield return new object[] { null, "/home/sshnet/scp test", "file 123", 1024 }; + yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'sp\u0100ce \\tab\tlf\n*?[#~=%", "file123", 1024 }; + yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/scp-test", "file|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%", 1024 }; + } + + private static IEnumerable GetScpUploadFileStreamDirectoryDoesNotExistData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-issue289", "file123" }; + } + + private static IEnumerable GetScpUploadFileStreamExistingDirectoryData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-issue286" }; + } + + private static IEnumerable GetScpUploadFileInfoDirectoryDoesNotExistData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-issue289", "file123" }; + } + + private static IEnumerable GetScpUploadFileInfoExistingDirectoryData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-issue286" }; + } + + private static IEnumerable GetScpUploadFileInfoExistingFileData() + { + yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-upload-file" }; + } + + private static IEnumerable GetScpUploadDirectoryInfoDirectoryDoesNotExistData() + { + yield return new object[] { RemotePathTransformation.None, "scp-directorydoesnotexist" }; + } + + private static void CreateRemoteFile(ScpClient client, string remoteFile, int size) + { + var file = CreateTempFile(size); + + try + { + using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + client.Upload(fs, remoteFile); + } + } + finally + { + File.Delete(file); + } + } + + private static string GetAbsoluteRemotePath(SftpClient client, string directoryName, string fileName) + { + var absolutePath = string.Empty; + + if (directoryName.Length == 0) + { + absolutePath += client.WorkingDirectory; + } + else + { + if (directoryName[0] != '/') + { + absolutePath += client.WorkingDirectory + "/" + directoryName; + } + else + { + absolutePath = directoryName; + } + } + + return absolutePath + "/" + fileName; + } + + private static string GetAbsoluteRemotePath(IConnectionInfoFactory connectionInfoFactory, string directoryName) + { + var absolutePath = string.Empty; + + if (directoryName.Length == 0 || directoryName == ".") + { + using (var client = new SftpClient(connectionInfoFactory.Create())) + { + client.Connect(); + + absolutePath += client.WorkingDirectory; + } + } + else + { + if (directoryName[0] != '/') + { + using (var client = new SftpClient(connectionInfoFactory.Create())) + { + client.Connect(); + + absolutePath += client.WorkingDirectory + "/" + directoryName; + } + } + else + { + absolutePath = directoryName; + } + } + + return absolutePath; + } + + private static string CreateRemoteFileHash(SftpClient client, string remotePath) + { + using (var fs = client.OpenRead(remotePath)) + { + return CreateHash(fs); + } + } + + private static string CombinePaths(string path1, string path2) + { + if (path1.Length == 0) + { + return path2; + } + + if (path2.Length == 0) + { + return path1; + } + + return path1 + "/" + path2; + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/SftpClientTests.cs b/src/Renci.SshNet.IntegrationTests/SftpClientTests.cs index 9e91ad70d..ee0258cdc 100644 --- a/src/Renci.SshNet.IntegrationTests/SftpClientTests.cs +++ b/src/Renci.SshNet.IntegrationTests/SftpClientTests.cs @@ -1,7 +1,6 @@ -using Renci.SshNet; using Renci.SshNet.Common; -namespace IntegrationTests +namespace Renci.SshNet.IntegrationTests { /// /// The SFTP client integration tests diff --git a/src/Renci.SshNet.IntegrationTests/SftpTests.cs b/src/Renci.SshNet.IntegrationTests/SftpTests.cs new file mode 100644 index 000000000..dc316cfbc --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/SftpTests.cs @@ -0,0 +1,6234 @@ +using System.Diagnostics; + +using Renci.SshNet.Common; +using Renci.SshNet.IntegrationTests.Common; +using Renci.SshNet.Sftp; + +namespace Renci.SshNet.IntegrationTests +{ + // TODO: DeleteDirectory (fail + success + // TODO: DeleteFile (fail + success + // TODO: Delete (fail + success + + [TestClass] + public class SftpTests : TestBase + { + private IConnectionInfoFactory _connectionInfoFactory; + private IConnectionInfoFactory _adminConnectionInfoFactory; + private IRemotePathTransformation _remotePathTransformation; + + [TestInitialize] + public void SetUp() + { + _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort); + _adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort); + _remotePathTransformation = RemotePathTransformation.ShellQuote; + } + +#if FEATURE_MSTEST_DATATEST + [DataTestMethod] + [DynamicData(nameof(GetSftpUploadFileFileStreamData), DynamicDataSourceType.Method)] +#else + [TestMethod] + public void Sftp_Upload_DirectoryInfo_ExistingFile() + { + foreach (var data in GetSftpUploadFileFileStreamData()) + { + Sftp_UploadFile_FileStream((int) data[0]); + } + } +#endif + public void Sftp_UploadFile_FileStream(int size) + { + var file = CreateTempFile(size); + + using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.Delete(remoteFile); + } + + try + { + client.UploadFile(fs, remoteFile); + + using (var memoryStream = new MemoryStream(size)) + { + client.DownloadFile(remoteFile, memoryStream); + memoryStream.Position = 0; + Assert.AreEqual(CreateFileHash(file), CreateHash(memoryStream)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.Delete(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ConnectDisconnect_Serial() + { + const int iterations = 100; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + for (var i = 1; i <= iterations; i++) + { + client.Connect(); + client.Disconnect(); + } + } + } + + [TestMethod] + public void Sftp_ConnectDisconnect_Parallel() + { + const int iterations = 10; + const int threads = 20; + + var startEvent = new ManualResetEvent(false); + + var tasks = Enumerable.Range(1, threads).Select(i => + { + return Task.Factory.StartNew(() => + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + startEvent.WaitOne(); + + for (var j = 0; j < iterations; j++) + { + client.Connect(); + client.Disconnect(); + } + } + }); + }).ToArray(); + + startEvent.Set(); + + Task.WaitAll(tasks); + } + + [TestMethod] + public void Sftp_BeginUploadFile() + { + const string content = "SftpBeginUploadFile"; + + var expectedByteCount = (ulong) Encoding.ASCII.GetByteCount(content); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.Delete(remoteFile); + } + + try + { + using (var memoryStream = new MemoryStream(Encoding.ASCII.GetBytes(content))) + { + IAsyncResult asyncResultCallback = null; + + var asyncResult = client.BeginUploadFile(memoryStream, remoteFile, ar => asyncResultCallback = ar); + + Assert.IsTrue(asyncResult.AsyncWaitHandle.WaitOne(10000)); + + // check async result + var sftpUploadAsyncResult = asyncResult as SftpUploadAsyncResult; + Assert.IsNotNull(sftpUploadAsyncResult); + Assert.IsFalse(sftpUploadAsyncResult.IsUploadCanceled); + Assert.IsTrue(sftpUploadAsyncResult.IsCompleted); + Assert.IsFalse(sftpUploadAsyncResult.CompletedSynchronously); + Assert.AreEqual(expectedByteCount, sftpUploadAsyncResult.UploadedBytes); + + // check async result callback + var sftpUploadAsyncResultCallback = asyncResultCallback as SftpUploadAsyncResult; + Assert.IsNotNull(sftpUploadAsyncResultCallback); + Assert.IsFalse(sftpUploadAsyncResultCallback.IsUploadCanceled); + Assert.IsTrue(sftpUploadAsyncResultCallback.IsCompleted); + Assert.IsFalse(sftpUploadAsyncResultCallback.CompletedSynchronously); + Assert.AreEqual(expectedByteCount, sftpUploadAsyncResultCallback.UploadedBytes); + } + + // check uploaded file + using (var memoryStream = new MemoryStream()) + { + client.DownloadFile(remoteFile, memoryStream); + memoryStream.Position = 0; + var remoteContent = Encoding.ASCII.GetString(memoryStream.ToArray()); + Assert.AreEqual(content, remoteContent); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.Delete(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Create_ExistingFile() + { + var encoding = Encoding.UTF8; + var initialContent = "Gert & Ann & Lisa"; + var newContent1 = "Sofie"; + var newContent1Bytes = GetBytesWithPreamble(newContent1, encoding); + var newContent2 = "Lisa & Sofie"; + var newContent2Bytes = GetBytesWithPreamble(newContent2, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent); + + #region Write less bytes than the current content, overwriting part of that content + + using (var fs = client.Create(remoteFile)) + using (var sw = new StreamWriter(fs, encoding)) + { + sw.Write(newContent1); + } + + var actualContent1 = client.ReadAllBytes(remoteFile); + Assert.IsTrue(newContent1Bytes.IsEqualTo(actualContent1)); + + #endregion Write less bytes than the current content, overwriting part of that content + + #region Write more bytes than the current content, overwriting and appending to that content + + using (var fs = client.Create(remoteFile)) + using (var sw = new StreamWriter(fs, encoding)) + { + sw.Write(newContent2); + } + + var actualContent2 = client.ReadAllBytes(remoteFile); + Assert.IsTrue(newContent2Bytes.IsEqualTo(actualContent2)); + + #endregion Write more bytes than the current content, overwriting and appending to that content + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Create_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + SftpFileStream fs = null; + + try + { + fs = client.Create(remoteFile); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + fs?.Dispose(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Create_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.BufferSize = 512 * 1024; + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var imageStream = GetResourceStream("Renci.SshNet.IntegrationTests.resources.issue #70.png")) + { + using (var fs = client.Create(remoteFile)) + { + byte[] buffer = new byte[Math.Min(client.BufferSize, imageStream.Length)]; + int bytesRead; + + while ((bytesRead = imageStream.Read(buffer, offset: 0, buffer.Length)) > 0) + { + fs.Write(buffer, offset: 0, bytesRead); + } + } + + using (var memoryStream = new MemoryStream()) + { + client.DownloadFile(remoteFile, memoryStream); + + memoryStream.Position = 0; + imageStream.Position = 0; + + Assert.AreEqual(CreateHash(imageStream), CreateHash(memoryStream)); + } + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendAllLines_NoEncoding_ExistingFile() + { + var initialContent = "\u0100ert & Ann"; + IEnumerable linesToAppend = new[] { "Forever", "&", "\u0116ver" }; + var expectedContent = initialContent + string.Join(Environment.NewLine, linesToAppend) + + Environment.NewLine; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent); + client.AppendAllLines(remoteFile, linesToAppend); + + var text = client.ReadAllText(remoteFile); + Assert.AreEqual(expectedContent, text); + + // ensure we didn't write an UTF-8 BOM + using (var fs = client.OpenRead(remoteFile)) + { + var firstByte = fs.ReadByte(); + Assert.AreEqual(Encoding.UTF8.GetBytes(expectedContent)[0], firstByte); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendAllLines_NoEncoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + IEnumerable linesToAppend = new[] { "\u0139isa", "&", "Sofie" }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.AppendAllLines(remoteFile, linesToAppend); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendAllLines_NoEncoding_FileDoesNotExist() + { + IEnumerable linesToAppend = new[] { "\u0139isa", "&", "Sofie" }; + var expectedContent = string.Join(Environment.NewLine, linesToAppend) + Environment.NewLine; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.AppendAllLines(remoteFile, linesToAppend); + + var text = client.ReadAllText(remoteFile); + Assert.AreEqual(expectedContent, text); + + // ensure we didn't write an UTF-8 BOM + using (var fs = client.OpenRead(remoteFile)) + { + var firstByte = fs.ReadByte(); + Assert.AreEqual(Encoding.UTF8.GetBytes(expectedContent)[0], firstByte); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendAllText_NoEncoding_ExistingFile() + { + var initialContent = "\u0100ert & Ann"; + var contentToAppend = "Forever&\u0116ver"; + var expectedContent = initialContent + contentToAppend; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent); + client.AppendAllText(remoteFile, contentToAppend); + + var text = client.ReadAllText(remoteFile); + Assert.AreEqual(expectedContent, text); + + // ensure we didn't write an UTF-8 BOM + using (var fs = client.OpenRead(remoteFile)) + { + var firstByte = fs.ReadByte(); + Assert.AreEqual(Encoding.UTF8.GetBytes(expectedContent)[0], firstByte); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendAllText_NoEncoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + var contentToAppend = "Forever&\u0116ver"; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.AppendAllText(remoteFile, contentToAppend); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendAllText_NoEncoding_FileDoesNotExist() + { + var contentToAppend = "Forever&\u0116ver"; + var expectedContent = contentToAppend; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.AppendAllText(remoteFile, contentToAppend); + + var text = client.ReadAllText(remoteFile); + Assert.AreEqual(expectedContent, text); + + // ensure we didn't write an UTF-8 BOM + using (var fs = client.OpenRead(remoteFile)) + { + var firstByte = fs.ReadByte(); + Assert.AreEqual(Encoding.UTF8.GetBytes(expectedContent)[0], firstByte); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendText_NoEncoding_ExistingFile() + { + var initialContent = "\u0100ert & Ann"; + var contentToAppend = "Forever&\u0116ver"; + var expectedContent = initialContent + contentToAppend; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent); + + using (var sw = client.AppendText(remoteFile)) + { + sw.Write(contentToAppend); + } + + var text = client.ReadAllText(remoteFile); + Assert.AreEqual(expectedContent, text); + + // ensure we didn't write an UTF-8 BOM + using (var fs = client.OpenRead(remoteFile)) + { + var firstByte = fs.ReadByte(); + Assert.AreEqual(Encoding.UTF8.GetBytes(expectedContent)[0], firstByte); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendText_NoEncoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + StreamWriter sw = null; + + try + { + sw = client.AppendText(remoteFile); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + sw?.Dispose(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendText_NoEncoding_FileDoesNotExist() + { + var contentToAppend = "\u0100ert & Ann"; + var expectedContent = contentToAppend; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var sw = client.AppendText(remoteFile)) + { + sw.Write(contentToAppend); + } + + // ensure we didn't write an UTF-8 BOM + using (var fs = client.OpenRead(remoteFile)) + { + var firstByte = fs.ReadByte(); + Assert.AreEqual(Encoding.UTF8.GetBytes(expectedContent)[0], firstByte); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendAllLines_Encoding_ExistingFile() + { + var initialContent = "\u0100ert & Ann"; + IEnumerable linesToAppend = new[] { "Forever", "&", "\u0116ver" }; + var expectedContent = initialContent + string.Join(Environment.NewLine, linesToAppend) + + Environment.NewLine; + var encoding = GetRandomEncoding(); + var expectedBytes = GetBytesWithPreamble(expectedContent, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent, encoding); + client.AppendAllLines(remoteFile, linesToAppend, encoding); + + var text = client.ReadAllText(remoteFile, encoding); + Assert.AreEqual(expectedContent, text); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendAllLines_Encoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + IEnumerable linesToAppend = new[] { "Forever", "&", "\u0116ver" }; + var encoding = GetRandomEncoding(); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.AppendAllLines(remoteFile, linesToAppend, encoding); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendAllLines_Encoding_FileDoesNotExist() + { + IEnumerable linesToAppend = new[] { "\u0139isa", "&", "Sofie" }; + var expectedContent = string.Join(Environment.NewLine, linesToAppend) + Environment.NewLine; + var encoding = GetRandomEncoding(); + var expectedBytes = GetBytesWithPreamble(expectedContent, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.AppendAllLines(remoteFile, linesToAppend, encoding); + + var text = client.ReadAllText(remoteFile, encoding); + Assert.AreEqual(expectedContent, text); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendAllText_Encoding_ExistingFile() + { + var initialContent = "\u0100ert & Ann"; + var contentToAppend = "Forever&\u0116ver"; + var expectedContent = initialContent + contentToAppend; + var encoding = GetRandomEncoding(); + var expectedBytes = GetBytesWithPreamble(expectedContent, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent, encoding); + client.AppendAllText(remoteFile, contentToAppend, encoding); + + var text = client.ReadAllText(remoteFile, encoding); + Assert.AreEqual(expectedContent, text); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendAllText_Encoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + const string contentToAppend = "Forever&\u0116ver"; + var encoding = GetRandomEncoding(); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.AppendAllText(remoteFile, contentToAppend, encoding); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendAllText_Encoding_FileDoesNotExist() + { + const string contentToAppend = "Forever&\u0116ver"; + var expectedContent = contentToAppend; + var encoding = GetRandomEncoding(); + var expectedBytes = GetBytesWithPreamble(expectedContent, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.AppendAllText(remoteFile, contentToAppend, encoding); + + var text = client.ReadAllText(remoteFile, encoding); + Assert.AreEqual(expectedContent, text); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendText_Encoding_ExistingFile() + { + const string initialContent = "\u0100ert & Ann"; + const string contentToAppend = "Forever&\u0116ver"; + var expectedContent = initialContent + contentToAppend; + var encoding = GetRandomEncoding(); + var expectedBytes = GetBytesWithPreamble(expectedContent, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent, encoding); + + using (var sw = client.AppendText(remoteFile, encoding)) + { + sw.Write(contentToAppend); + } + + var text = client.ReadAllText(remoteFile, encoding); + Assert.AreEqual(expectedContent, text); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + } + } + } + + [TestMethod] + public void Sftp_AppendText_Encoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + var encoding = GetRandomEncoding(); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + StreamWriter sw = null; + + try + { + sw = client.AppendText(remoteFile, encoding); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + sw?.Dispose(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_AppendText_Encoding_FileDoesNotExist() + { + var encoding = GetRandomEncoding(); + const string contentToAppend = "\u0100ert & Ann"; + var expectedBytes = GetBytesWithPreamble(contentToAppend, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var sw = client.AppendText(remoteFile, encoding)) + { + sw.Write(contentToAppend); + } + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_CreateText_NoEncoding_ExistingFile() + { + var encoding = new UTF8Encoding(false, true); + const string initialContent = "\u0100ert & Ann"; + var initialContentBytes = GetBytesWithPreamble(initialContent, encoding); + const string newContent = "\u0116ver"; + const string expectedContent = "\u0116ver" + " & Ann"; + var expectedContentBytes = GetBytesWithPreamble(expectedContent, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent); + + using (client.CreateText(remoteFile)) + { + } + + // verify that original content is left untouched + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(initialContentBytes.IsEqualTo(actualBytes)); + } + + // write content that is less bytes than original content + using (var sw = client.CreateText(remoteFile)) + { + sw.Write(newContent); + } + + // verify that original content is only partially overwritten + var text = client.ReadAllText(remoteFile); + Assert.AreEqual(expectedContent, text); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedContentBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_CreateText_NoEncoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + StreamWriter sw = null; + + try + { + sw = client.CreateText(remoteFile); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + sw?.Dispose(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_CreateText_NoEncoding_FileDoesNotExist() + { + var encoding = new UTF8Encoding(false, true); + var initialContent = "\u0100ert & Ann"; + var initialContentBytes = GetBytesWithPreamble(initialContent, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (client.CreateText(remoteFile)) + { + } + + // verify that empty file was created + Assert.IsTrue(client.Exists(remoteFile)); + + var file = client.GetAttributes(remoteFile); + Assert.AreEqual(0, file.Size); + + client.DeleteFile(remoteFile); + + using (var sw = client.CreateText(remoteFile)) + { + sw.Write(initialContent); + } + + // verify that content is written to file + var text = client.ReadAllText(remoteFile); + Assert.AreEqual(initialContent, text); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(initialContentBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_CreateText_Encoding_ExistingFile() + { + var encoding = GetRandomEncoding(); + var initialContent = "\u0100ert & Ann"; + var initialContentBytes = GetBytesWithPreamble(initialContent, encoding); + var newContent = "\u0116ver"; + var expectedContent = "\u0116ver" + " & Ann"; + var expectedContentBytes = GetBytesWithPreamble(expectedContent, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent, encoding); + + using (client.CreateText(remoteFile)) + { + } + + // verify that original content is left untouched + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(initialContentBytes.IsEqualTo(actualBytes)); + } + + // write content that is less bytes than original content + using (var sw = client.CreateText(remoteFile, encoding)) + { + sw.Write(newContent); + } + + // verify that original content is only partially overwritten + var text = client.ReadAllText(remoteFile, encoding); + Assert.AreEqual(expectedContent, text); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedContentBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_CreateText_Encoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + var encoding = GetRandomEncoding(); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + StreamWriter sw = null; + + try + { + sw = client.CreateText(remoteFile, encoding); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + sw?.Dispose(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_CreateText_Encoding_FileDoesNotExist() + { + var encoding = GetRandomEncoding(); + var initialContent = "\u0100ert & Ann"; + var initialContentBytes = GetBytesWithPreamble(initialContent, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (client.CreateText(remoteFile, encoding)) + { + } + + // verify that file containing only preamble was created + Assert.IsTrue(client.Exists(remoteFile)); + + var file = client.GetAttributes(remoteFile); + Assert.AreEqual(encoding.GetPreamble().Length, file.Size); + + client.DeleteFile(remoteFile); + + using (var sw = client.CreateText(remoteFile, encoding)) + { + sw.Write(initialContent); + } + + // verify that content is written to file + var text = client.ReadAllText(remoteFile, encoding); + Assert.AreEqual(initialContent, text); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(initialContentBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_DownloadFile_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + using (var ms = new MemoryStream()) + { + try + { + client.DownloadFile(remoteFile, ms); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + + // ensure file was not created by us + Assert.IsFalse(client.Exists(remoteFile)); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + } + + [TestMethod] + public void Sftp_ReadAllBytes_ExistingFile() + { + var encoding = GetRandomEncoding(); + var content = "\u0100ert & Ann"; + var contentBytes = GetBytesWithPreamble(content, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, content, encoding); + + var actualBytes = client.ReadAllBytes(remoteFile); + Assert.IsTrue(contentBytes.IsEqualTo(actualBytes)); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadAllBytes_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.ReadAllBytes(remoteFile); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + + // ensure file was not created by us + Assert.IsFalse(client.Exists(remoteFile)); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadAllLines_NoEncoding_ExistingFile() + { + var encoding = new UTF8Encoding(false, true); + var lines = new[] { "\u0100ert & Ann", "Forever", "&", "\u0116ver" }; + var linesBytes = GetBytesWithPreamble(string.Join(Environment.NewLine, lines) + Environment.NewLine, + encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var sw = client.AppendText(remoteFile)) + { + for (var i = 0; i < lines.Length; i++) + { + sw.WriteLine(lines[i]); + } + } + + var actualLines = client.ReadAllLines(remoteFile); + Assert.IsNotNull(actualLines); + Assert.AreEqual(lines.Length, actualLines.Length); + + for (var i = 0; i < lines.Length; i++) + { + Assert.AreEqual(lines[i], actualLines[i]); + } + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(linesBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadAllLines_NoEncoding_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.ReadAllLines(remoteFile); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + + // ensure file was not created by us + Assert.IsFalse(client.Exists(remoteFile)); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadAllLines_Encoding_ExistingFile() + { + var encoding = GetRandomEncoding(); + var lines = new[] { "\u0100ert & Ann", "Forever", "&", "\u0116ver" }; + var linesBytes = GetBytesWithPreamble(string.Join(Environment.NewLine, lines) + Environment.NewLine, + encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var sw = client.AppendText(remoteFile, encoding)) + { + for (var i = 0; i < lines.Length; i++) + { + sw.WriteLine(lines[i]); + } + } + + var actualLines = client.ReadAllLines(remoteFile, encoding); + Assert.IsNotNull(actualLines); + Assert.AreEqual(lines.Length, actualLines.Length); + + for (var i = 0; i < lines.Length; i++) + { + Assert.AreEqual(lines[i], actualLines[i]); + } + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(linesBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadAllLines_Encoding_FileDoesNotExist() + { + var encoding = GetRandomEncoding(); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.ReadAllLines(remoteFile, encoding); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + + // ensure file was not created by us + Assert.IsFalse(client.Exists(remoteFile)); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadAllText_NoEncoding_ExistingFile() + { + var encoding = new UTF8Encoding(false, true); + var lines = new[] { "\u0100ert & Ann", "Forever", "&", "\u0116ver" }; + var expectedText = string.Join(Environment.NewLine, lines) + Environment.NewLine; + var expectedBytes = GetBytesWithPreamble(expectedText, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var sw = client.AppendText(remoteFile)) + { + for (var i = 0; i < lines.Length; i++) + { + sw.WriteLine(lines[i]); + } + } + + var actualText = client.ReadAllText(remoteFile); + Assert.AreEqual(actualText, expectedText); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadAllText_NoEncoding_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.ReadAllText(remoteFile); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + + // ensure file was not created by us + Assert.IsFalse(client.Exists(remoteFile)); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadAllText_Encoding_ExistingFile() + { + var encoding = GetRandomEncoding(); + var lines = new[] { "\u0100ert & Ann", "Forever", "&", "\u0116ver" }; + var expectedText = string.Join(Environment.NewLine, lines) + Environment.NewLine; + var expectedBytes = GetBytesWithPreamble(expectedText, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var sw = client.AppendText(remoteFile, encoding)) + { + for (var i = 0; i < lines.Length; i++) + { + sw.WriteLine(lines[i]); + } + } + + var actualText = client.ReadAllText(remoteFile, encoding); + Assert.AreEqual(expectedText, actualText); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadAllText_Encoding_FileDoesNotExist() + { + var encoding = GetRandomEncoding(); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.ReadAllText(remoteFile, encoding); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + + // ensure file was not created by us + Assert.IsFalse(client.Exists(remoteFile)); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadLines_NoEncoding_ExistingFile() + { + var lines = new[] { "\u0100ert & Ann", "Forever", "&", "\u0116ver" }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var sw = client.AppendText(remoteFile)) + { + for (var i = 0; i < lines.Length; i++) + { + sw.WriteLine(lines[i]); + } + } + + var actualLines = client.ReadLines(remoteFile); + Assert.IsNotNull(actualLines); + + var actualLinesEnum = actualLines.GetEnumerator(); + for (var i = 0; i < lines.Length; i++) + { + Assert.IsTrue(actualLinesEnum.MoveNext()); + var actualLine = actualLinesEnum.Current; + Assert.AreEqual(lines[i], actualLine); + } + + Assert.IsFalse(actualLinesEnum.MoveNext()); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadLines_NoEncoding_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.ReadLines(remoteFile); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + + // ensure file was not created by us + Assert.IsFalse(client.Exists(remoteFile)); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadLines_Encoding_ExistingFile() + { + var encoding = GetRandomEncoding(); + var lines = new[] { "\u0100ert & Ann", "Forever", "&", "\u0116ver" }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var sw = client.AppendText(remoteFile, encoding)) + { + for (var i = 0; i < lines.Length; i++) + { + sw.WriteLine(lines[i]); + } + } + + var actualLines = client.ReadLines(remoteFile, encoding); + Assert.IsNotNull(actualLines); + + using (var actualLinesEnum = actualLines.GetEnumerator()) + { + for (var i = 0; i < lines.Length; i++) + { + Assert.IsTrue(actualLinesEnum.MoveNext()); + + var actualLine = actualLinesEnum.Current; + Assert.AreEqual(lines[i], actualLine); + } + + Assert.IsFalse(actualLinesEnum.MoveNext()); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_ReadLines_Encoding_FileDoesNotExist() + { + var encoding = GetRandomEncoding(); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.ReadLines(remoteFile, encoding); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + + // ensure file was not created by us + Assert.IsFalse(client.Exists(remoteFile)); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllBytes_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + var content = GenerateRandom(size: 5); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllBytes(remoteFile, content); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllBytes_ExistingFile() + { + var initialContent = GenerateRandom(size: 13); + var newContent1 = GenerateRandom(size: 5); + var expectedContent1 = new ArrayBuilder().Add(newContent1) + .Add(initialContent, newContent1.Length, initialContent.Length - newContent1.Length) + .Build(); + var newContent2 = GenerateRandom(size: 50000); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var fs = client.Create(remoteFile)) + { + fs.Write(initialContent, offset: 0, initialContent.Length); + } + + #region Write less bytes than the current content, overwriting part of that content + + client.WriteAllBytes(remoteFile, newContent1); + + var actualContent1 = client.ReadAllBytes(remoteFile); + Assert.IsTrue(expectedContent1.IsEqualTo(actualContent1)); + + #endregion Write less bytes than the initial content, overwriting part of that content + + #region Write more bytes than the current content, overwriting and appending to that content + + client.WriteAllBytes(remoteFile, newContent2); + + var actualContent2 = client.ReadAllBytes(remoteFile); + Assert.IsTrue(newContent2.IsEqualTo(actualContent2)); + + #endregion Write less bytes than the initial content, overwriting part of that content + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllBytes_FileDoesNotExist() + { + var content = GenerateRandom(size: 13); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllBytes(remoteFile, content); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(content.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + + [TestMethod] + public void Sftp_WriteAllLines_IEnumerable_NoEncoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + IEnumerable linesToWrite = new[] { "Forever", "&", "\u0116ver" }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllLines(remoteFile, linesToWrite); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllLines_IEnumerable_NoEncoding_ExistingFile() + { + var encoding = new UTF8Encoding(false, true); + var initialContent = "\u0100ert & Ann Forever & Ever Lisa & Sofie"; + var initialContentBytes = GetBytesWithPreamble(initialContent, encoding); + IEnumerable linesToWrite1 = new[] { "Forever", "&", "\u0116ver" }; + var linesToWrite1Bytes = + GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite1) + Environment.NewLine, encoding); + var expectedBytes1 = new ArrayBuilder().Add(linesToWrite1Bytes) + .Add(initialContentBytes, + linesToWrite1Bytes.Length, + initialContentBytes.Length - linesToWrite1Bytes.Length) + .Build(); + IEnumerable linesToWrite2 = new[] { "Forever", "&", "\u0116ver", "Gert & Ann", "Lisa + Sofie" }; + var linesToWrite2Bytes = + GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite2) + Environment.NewLine, encoding); + var expectedBytes2 = linesToWrite2Bytes; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + // create initial content + client.WriteAllText(remoteFile, initialContent); + + #region Write less bytes than the current content, overwriting part of that content + + client.WriteAllLines(remoteFile, linesToWrite1); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes1.IsEqualTo(actualBytes)); + } + + #endregion Write less bytes than the current content, overwriting part of that content + + #region Write more bytes than the current content, overwriting and appending to that content + + client.WriteAllLines(remoteFile, linesToWrite2); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes2.IsEqualTo(actualBytes)); + } + + #endregion Write more bytes than the current content, overwriting and appending to that content + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllLines_IEnumerable_NoEncoding_FileDoesNotExist() + { + var encoding = new UTF8Encoding(false, true); + IEnumerable linesToWrite = new[] { "\u0139isa", "&", "Sofie" }; + var linesToWriteBytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite) + Environment.NewLine, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllLines(remoteFile, linesToWrite); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(linesToWriteBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllLines_IEnumerable_Encoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + var encoding = GetRandomEncoding(); + IEnumerable linesToWrite = new[] { "Forever", "&", "\u0116ver" }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllLines(remoteFile, linesToWrite, encoding); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllLines_IEnumerable_Encoding_ExistingFile() + { + var encoding = GetRandomEncoding(); + const string initialContent = "\u0100ert & Ann Forever & Ever Lisa & Sofie"; + var initialContentBytes = GetBytesWithPreamble(initialContent, encoding); + IEnumerable linesToWrite1 = new[] { "Forever", "&", "\u0116ver" }; + var linesToWrite1Bytes = + GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite1) + Environment.NewLine, encoding); + var expectedBytes1 = new ArrayBuilder().Add(linesToWrite1Bytes) + .Add(initialContentBytes, + linesToWrite1Bytes.Length, + initialContentBytes.Length - linesToWrite1Bytes.Length) + .Build(); + IEnumerable linesToWrite2 = new[] { "Forever", "&", "\u0116ver", "Gert & Ann", "Lisa + Sofie" }; + var linesToWrite2Bytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite2) + Environment.NewLine, encoding); + var expectedBytes2 = linesToWrite2Bytes; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + // create initial content + client.WriteAllText(remoteFile, initialContent, encoding); + + #region Write less bytes than the current content, overwriting part of that content + + client.WriteAllLines(remoteFile, linesToWrite1, encoding); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes1.IsEqualTo(actualBytes)); + } + + #endregion Write less bytes than the current content, overwriting part of that content + + #region Write more bytes than the current content, overwriting and appending to that content + + client.WriteAllLines(remoteFile, linesToWrite2, encoding); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes2.IsEqualTo(actualBytes)); + } + + #endregion Write more bytes than the current content, overwriting and appending to that content + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllLines_IEnumerable_Encoding_FileDoesNotExist() + { + var encoding = GetRandomEncoding(); + IEnumerable linesToWrite = new[] { "\u0139isa", "&", "Sofie" }; + var linesToWriteBytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite) + Environment.NewLine, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllLines(remoteFile, linesToWrite, encoding); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(linesToWriteBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllLines_Array_NoEncoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + var linesToWrite = new[] { "Forever", "&", "\u0116ver" }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllLines(remoteFile, linesToWrite); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllLines_Array_NoEncoding_ExistingFile() + { + var encoding = new UTF8Encoding(false, true); + const string initialContent = "\u0100ert & Ann Forever & Ever Lisa & Sofie"; + var initialContentBytes = GetBytesWithPreamble(initialContent, encoding); + var linesToWrite1 = new[] { "Forever", "&", "\u0116ver" }; + var linesToWrite1Bytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite1) + Environment.NewLine, encoding); + var expectedBytes1 = new ArrayBuilder().Add(linesToWrite1Bytes) + .Add(initialContentBytes, linesToWrite1Bytes.Length, initialContentBytes.Length - linesToWrite1Bytes.Length) + .Build(); + var linesToWrite2 = new[] { "Forever", "&", "\u0116ver", "Gert & Ann", "Lisa + Sofie" }; + var linesToWrite2Bytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite2) + Environment.NewLine, encoding); + var expectedBytes2 = linesToWrite2Bytes; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + // create initial content + client.WriteAllText(remoteFile, initialContent); + + #region Write less bytes than the current content, overwriting part of that content + + client.WriteAllLines(remoteFile, linesToWrite1); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes1.IsEqualTo(actualBytes)); + } + + #endregion Write less bytes than the current content, overwriting part of that content + + #region Write more bytes than the current content, overwriting and appending to that content + + client.WriteAllLines(remoteFile, linesToWrite2); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes2.IsEqualTo(actualBytes)); + } + + #endregion Write more bytes than the current content, overwriting and appending to that content + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllLines_Array_NoEncoding_FileDoesNotExist() + { + var encoding = new UTF8Encoding(false, true); + var linesToWrite = new[] { "\u0139isa", "&", "Sofie" }; + var linesToWriteBytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite) + Environment.NewLine, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllLines(remoteFile, linesToWrite); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(linesToWriteBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllLines_Array_Encoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + var encoding = GetRandomEncoding(); + var linesToWrite = new[] { "Forever", "&", "\u0116ver" }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllLines(remoteFile, linesToWrite, encoding); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllLines_Array_Encoding_ExistingFile() + { + const string initialContent = "\u0100ert & Ann Forever & Ever Lisa & Sofie"; + + var encoding = GetRandomEncoding(); + var initialContentBytes = GetBytesWithPreamble(initialContent, encoding); + var linesToWrite1 = new[] { "Forever", "&", "\u0116ver" }; + var linesToWrite1Bytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite1) + Environment.NewLine, encoding); + var expectedBytes1 = new ArrayBuilder().Add(linesToWrite1Bytes) + .Add(initialContentBytes, linesToWrite1Bytes.Length, initialContentBytes.Length - linesToWrite1Bytes.Length) + .Build(); + var linesToWrite2 = new[] { "Forever", "&", "\u0116ver", "Gert & Ann", "Lisa + Sofie" }; + var linesToWrite2Bytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite2) + Environment.NewLine, encoding); + var expectedBytes2 = linesToWrite2Bytes; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + // create initial content + client.WriteAllText(remoteFile, initialContent, encoding); + + #region Write less bytes than the current content, overwriting part of that content + + client.WriteAllLines(remoteFile, linesToWrite1, encoding); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes1.IsEqualTo(actualBytes)); + } + + #endregion Write less bytes than the current content, overwriting part of that content + + #region Write more bytes than the current content, overwriting and appending to that content + + client.WriteAllLines(remoteFile, linesToWrite2, encoding); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes2.IsEqualTo(actualBytes)); + } + + #endregion Write more bytes than the current content, overwriting and appending to that content + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllLines_Array_Encoding_FileDoesNotExist() + { + var encoding = GetRandomEncoding(); + var linesToWrite = new[] { "\u0139isa", "&", "Sofie" }; + var linesToWriteBytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite) + Environment.NewLine, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllLines(remoteFile, linesToWrite, encoding); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(linesToWriteBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + + } + } + + [TestMethod] + public void Sftp_WriteAllText_NoEncoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + const string initialContent = "\u0100ert & Ann Forever & \u0116ver Lisa & Sofie"; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllText_NoEncoding_ExistingFile() + { + const string initialContent = "\u0100ert & Ann Forever & \u0116ver Lisa & Sofie"; + const string newContent1 = "For\u0116ver & Ever"; + + var encoding = new UTF8Encoding(false, true); + var initialContentBytes = GetBytesWithPreamble(initialContent, encoding); + var newContent1Bytes = GetBytesWithPreamble(newContent1, encoding); + var expectedBytes1 = new ArrayBuilder().Add(newContent1Bytes) + .Add(initialContentBytes, newContent1Bytes.Length, initialContentBytes.Length - newContent1Bytes.Length) + .Build(); + var newContent2 = "Sofie & Lisa For\u0116ver & Ever with \u0100ert & Ann"; + var newContent2Bytes = GetBytesWithPreamble(newContent2, encoding); + var expectedBytes2 = newContent2Bytes; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent); + + #region Write less bytes than the current content, overwriting part of that content + + client.WriteAllText(remoteFile, newContent1); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes1.IsEqualTo(actualBytes)); + } + + #endregion Write less bytes than the current content, overwriting part of that content + + #region Write more bytes than the current content, overwriting and appending to that content + + client.WriteAllText(remoteFile, newContent2); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes2.IsEqualTo(actualBytes)); + } + + #endregion Write more bytes than the current content, overwriting and appending to that content + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllText_NoEncoding_FileDoesNotExist() + { + const string initialContent = "\u0100ert & Ann Forever & \u0116ver Lisa & Sofie"; + + var encoding = new UTF8Encoding(false, true); + var initialContentBytes = GetBytesWithPreamble(initialContent, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(initialContentBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllText_Encoding_DirectoryDoesNotExist() + { + const string remoteFile = "/home/sshnet/directorydoesnotexist/test"; + + var encoding = GetRandomEncoding(); + const string content = "For\u0116ver & Ever"; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, content, encoding); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllText_Encoding_ExistingFile() + { + const string initialContent = "\u0100ert & Ann Forever & \u0116ver Lisa & Sofie"; + const string newContent1 = "For\u0116ver & Ever"; + const string newContent2 = "Sofie & Lisa For\u0116ver & Ever with \u0100ert & Ann"; + + var encoding = GetRandomEncoding(); + var initialContentBytes = GetBytesWithPreamble(initialContent, encoding); + var newContent1Bytes = GetBytesWithPreamble(newContent1, encoding); + var expectedBytes1 = new ArrayBuilder().Add(newContent1Bytes) + .Add(initialContentBytes, newContent1Bytes.Length, initialContentBytes.Length - newContent1Bytes.Length) + .Build(); + var newContent2Bytes = GetBytesWithPreamble(newContent2, encoding); + var expectedBytes2 = newContent2Bytes; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent, encoding); + + #region Write less bytes than the current content, overwriting part of that content + + client.WriteAllText(remoteFile, newContent1, encoding); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes1.IsEqualTo(actualBytes)); + } + + #endregion Write less bytes than the current content, overwriting part of that content + + #region Write more bytes than the current content, overwriting and appending to that content + + client.WriteAllText(remoteFile, newContent2, encoding); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(expectedBytes2.IsEqualTo(actualBytes)); + } + + #endregion Write more bytes than the current content, overwriting and appending to that content + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_WriteAllText_Encoding_FileDoesNotExist() + { + const string initialContent = "\u0100ert & Ann Forever & \u0116ver Lisa & Sofie"; + + var encoding = GetRandomEncoding(); + var initialContentBytes = GetBytesWithPreamble(initialContent, encoding); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, initialContent, encoding); + + using (var fs = client.OpenRead(remoteFile)) + { + var actualBytes = new byte[fs.Length]; + fs.Read(actualBytes, offset: 0, actualBytes.Length); + Assert.IsTrue(initialContentBytes.IsEqualTo(actualBytes)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_BeginDownloadFile_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var ms = new MemoryStream()) + { + var asyncResult = client.BeginDownloadFile(remoteFile, ms); + try + { + client.EndDownloadFile(asyncResult); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + + // ensure file was not created by us + Assert.IsFalse(client.Exists(remoteFile)); + } + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_BeginListDirectory_DirectoryDoesNotExist() + { + const string remoteDirectory = "/home/sshnet/test123"; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory))) + { + command.Execute(); + } + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var asyncResult = client.BeginListDirectory(remoteDirectory, null, null); + try + { + client.EndListDirectory(asyncResult); + Assert.Fail(); + } + catch (SftpPathNotFoundException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("No such file", ex.Message); + + // ensure directory was not created by us + Assert.IsFalse(client.Exists(remoteDirectory)); + } + } + } + + [TestMethod] + public void Sftp_BeginUploadFile_InputAndPath_DirectoryDoesNotExist() + { + const int size = 50 * 1024 * 1024; + const string remoteDirectory = "/home/sshnet/test123"; + const string remoteFile = remoteDirectory + "/test"; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory))) + { + command.Execute(); + } + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var memoryStream = CreateMemoryStream(size); + memoryStream.Position = 0; + + var asyncResult = client.BeginUploadFile(memoryStream, remoteFile); + try + { + client.EndUploadFile(asyncResult); + Assert.Fail(); + } + catch (SftpPathNotFoundException) + { + } + } + } + + [TestMethod] + public void Sftp_BeginUploadFile_InputAndPath_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + var uploadMemoryStream = new MemoryStream(); + var sw = new StreamWriter(uploadMemoryStream, Encoding.UTF8); + sw.Write("Gert & Ann"); + sw.Flush(); + uploadMemoryStream.Position = 0; + + var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile); + client.EndUploadFile(asyncResult); + + using (var downloadMemoryStream = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloadMemoryStream); + + downloadMemoryStream.Position = 0; + + using (var sr = new StreamReader(downloadMemoryStream, Encoding.UTF8)) + { + var content = sr.ReadToEnd(); + Assert.AreEqual("Gert & Ann", content); + } + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_BeginUploadFile_InputAndPath_ExistingFile() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, "Gert & Ann & Lisa"); + + var uploadMemoryStream = new MemoryStream(); + var sw = new StreamWriter(uploadMemoryStream, Encoding.UTF8); + sw.Write("Ann & Gert"); + sw.Flush(); + uploadMemoryStream.Position = 0; + + var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile); + client.EndUploadFile(asyncResult); + + using (var downloadMemoryStream = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloadMemoryStream); + + downloadMemoryStream.Position = 0; + + using (var sr = new StreamReader(downloadMemoryStream, Encoding.UTF8)) + { + var content = sr.ReadToEnd(); + Assert.AreEqual("Ann & Gert", content); + } + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsFalse_DirectoryDoesNotExist() + { + const int size = 50 * 1024 * 1024; + const string remoteDirectory = "/home/sshnet/test123"; + const string remoteFile = remoteDirectory + "/test"; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory))) + { + command.Execute(); + } + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var memoryStream = CreateMemoryStream(size); + memoryStream.Position = 0; + + var asyncResult = client.BeginUploadFile(memoryStream, remoteFile, false, null, null); + try + { + client.EndUploadFile(asyncResult); + Assert.Fail(); + } + catch (SftpPathNotFoundException) + { + } + } + } + + [TestMethod] + public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsFalse_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var uploadMemoryStream = new MemoryStream()) + using (var sw = new StreamWriter(uploadMemoryStream, Encoding.UTF8)) + { + sw.Write("Gert & Ann"); + sw.Flush(); + uploadMemoryStream.Position = 0; + + var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile, false, null, null); + client.EndUploadFile(asyncResult); + } + + using (var downloadMemoryStream = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloadMemoryStream); + + downloadMemoryStream.Position = 0; + + using (var sr = new StreamReader(downloadMemoryStream, Encoding.UTF8)) + { + var content = sr.ReadToEnd(); + Assert.AreEqual("Gert & Ann", content); + } + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsFalse_ExistingFile() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, "Gert & Ann & Lisa"); + + var uploadMemoryStream = new MemoryStream(); + var sw = new StreamWriter(uploadMemoryStream, Encoding.UTF8); + sw.Write("Ann & Gert"); + sw.Flush(); + uploadMemoryStream.Position = 0; + + var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile, false, null, null); + + try + { + client.EndUploadFile(asyncResult); + Assert.Fail(); + } + catch (SshException ex) + { + Assert.AreEqual(typeof(SshException), ex.GetType()); + Assert.IsNull(ex.InnerException); + Assert.AreEqual("Failure", ex.Message); + } + } + finally + { + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsTrue_DirectoryDoesNotExist() + { + const int size = 50 * 1024 * 1024; + const string remoteDirectory = "/home/sshnet/test123"; + const string remoteFile = remoteDirectory + "/test"; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory))) + { + command.Execute(); + } + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var memoryStream = CreateMemoryStream(size); + memoryStream.Position = 0; + + var asyncResult = client.BeginUploadFile(memoryStream, remoteFile, true, null, null); + try + { + client.EndUploadFile(asyncResult); + Assert.Fail(); + } + catch (SftpPathNotFoundException) + { + } + } + } + + [TestMethod] + public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsTrue_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var uploadMemoryStream = new MemoryStream()) + using (var sw = new StreamWriter(uploadMemoryStream, Encoding.UTF8)) + { + sw.Write("Gert & Ann"); + sw.Flush(); + uploadMemoryStream.Position = 0; + + var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile, true, null, null); + client.EndUploadFile(asyncResult); + } + + using (var downloadMemoryStream = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloadMemoryStream); + + downloadMemoryStream.Position = 0; + + using (var sr = new StreamReader(downloadMemoryStream, Encoding.UTF8)) + { + var content = sr.ReadToEnd(); + Assert.AreEqual("Gert & Ann", content); + } + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsTrue_ExistingFile() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllText(remoteFile, "Gert & Ann & Lisa"); + + using (var uploadMemoryStream = new MemoryStream()) + using (var sw = new StreamWriter(uploadMemoryStream, Encoding.UTF8)) + { + sw.Write("Ann & Gert"); + sw.Flush(); + uploadMemoryStream.Position = 0; + + var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile, true, null, null); + client.EndUploadFile(asyncResult); + } + + using (var downloadMemoryStream = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloadMemoryStream); + + downloadMemoryStream.Position = 0; + + using (var sr = new StreamReader(downloadMemoryStream, Encoding.UTF8)) + { + var content = sr.ReadToEnd(); + Assert.AreEqual("Ann & Gert", content); + } + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_UploadAndDownloadBigFile() + { + const int size = 50 * 1024 * 1024; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.Delete(remoteFile); + } + + try + { + var memoryStream = CreateMemoryStream(size); + memoryStream.Position = 0; + + client.UploadFile(memoryStream, remoteFile); + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + // check uploaded file + memoryStream = new MemoryStream(); + client.DownloadFile(remoteFile, memoryStream); + + Assert.AreEqual(size, memoryStream.Length); + + stopwatch.Stop(); + + Console.WriteLine(@"Elapsed: {0} ms", stopwatch.ElapsedMilliseconds); + Console.WriteLine(@"Transfer speed: {0:N2} KB/s", + CalculateTransferSpeed(memoryStream.Length, stopwatch.ElapsedMilliseconds)); + } + finally + { + if (client.Exists(remoteFile)) + { + client.Delete(remoteFile); + } + } + } + } + + /// + /// Issue 1672 + /// + [TestMethod] + public void Sftp_CurrentWorkingDirectory() + { + const string homeDirectory = "/home/sshnet"; + const string otherDirectory = homeDirectory + "/dir"; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(otherDirectory)) + { + client.DeleteDirectory(otherDirectory); + } + + try + { + client.CreateDirectory(otherDirectory); + client.ChangeDirectory(otherDirectory); + + using (var s = CreateStreamWithContent("A")) + { + client.UploadFile(s, "a.txt"); + } + + using (var s = new MemoryStream()) + { + client.DownloadFile("a.txt", s); + s.Position = 0; + + var content = Encoding.ASCII.GetString(s.ToArray()); + Assert.AreEqual("A", content); + } + + Assert.IsTrue(client.Exists(otherDirectory + "/a.txt")); + client.DeleteFile("a.txt"); + Assert.IsFalse(client.Exists(otherDirectory + "/a.txt")); + client.DeleteDirectory("."); + Assert.IsFalse(client.Exists(otherDirectory)); + } + finally + { + if (client.Exists(otherDirectory)) + { + client.DeleteDirectory(otherDirectory); + } + } + } + } + + [TestMethod] + public void Sftp_Exists() + { + const string remoteHome = "/home/sshnet"; + + #region Setup + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + #region Clean-up + + using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/DoesNotExist"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/symlink.to.directory.exists"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/directory.exists"}") + ) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/symlink.to.file.exists"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + using (var command = client.CreateCommand($"rm -f {remoteHome + "/file.exists"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + #endregion Clean-up + + #region Setup + + using (var command = client.CreateCommand($"touch {remoteHome + "/file.exists"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + using (var command = client.CreateCommand($"mkdir {remoteHome + "/directory.exists"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + using (var command = client.CreateCommand($"ln -s {remoteHome + "/file.exists"} {remoteHome + "/symlink.to.file.exists"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + using (var command = client.CreateCommand($"ln -s {remoteHome + "/directory.exists"} {remoteHome + "/symlink.to.directory.exists"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + #endregion Setup + } + + #endregion Setup + + #region Assert + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + Assert.IsFalse(client.Exists(remoteHome + "/DoesNotExist")); + Assert.IsTrue(client.Exists(remoteHome + "/file.exists")); + Assert.IsTrue(client.Exists(remoteHome + "/symlink.to.file.exists")); + Assert.IsTrue(client.Exists(remoteHome + "/directory.exists")); + Assert.IsTrue(client.Exists(remoteHome + "/symlink.to.directory.exists")); + } + + #endregion Assert + + #region Teardown + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/DoesNotExist"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/symlink.to.directory.exists"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/directory.exists"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/symlink.to.file.exists"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + + using (var command = client.CreateCommand($"rm -f {remoteHome + "/file.exists"}")) + { + command.Execute(); + Assert.AreEqual(0, command.ExitStatus, command.Error); + } + } + + #endregion Teardown + } + + [TestMethod] + public void Sftp_ListDirectory() + { + const string remoteDirectory = "/home/sshnet/test123"; + + try + { + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.RunCommand($@"rm -Rf ""{remoteDirectory}"""); + client.RunCommand($@"mkdir -p ""{remoteDirectory}"""); + client.RunCommand($@"mkdir -p ""{remoteDirectory}/sub"""); + client.RunCommand($@"touch ""{remoteDirectory}/file1"""); + client.RunCommand($@"touch ""{remoteDirectory}/file2"""); + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + client.ChangeDirectory(remoteDirectory); + + var directoryContent = client.ListDirectory(".").OrderBy(p => p.Name).ToList(); + Assert.AreEqual(5, directoryContent.Count); + + Assert.AreEqual(".", directoryContent[0].Name); + Assert.AreEqual($"{remoteDirectory}/.", directoryContent[0].FullName); + Assert.IsTrue(directoryContent[0].IsDirectory); + + Assert.AreEqual("..", directoryContent[1].Name); + Assert.AreEqual($"{remoteDirectory}/..", directoryContent[1].FullName); + Assert.IsTrue(directoryContent[1].IsDirectory); + + Assert.AreEqual("file1", directoryContent[2].Name); + Assert.AreEqual($"{remoteDirectory}/file1", directoryContent[2].FullName); + Assert.IsFalse(directoryContent[2].IsDirectory); + + Assert.AreEqual("file2", directoryContent[3].Name); + Assert.AreEqual($"{remoteDirectory}/file2", directoryContent[3].FullName); + Assert.IsFalse(directoryContent[3].IsDirectory); + + Assert.AreEqual("sub", directoryContent[4].Name); + Assert.AreEqual($"{remoteDirectory}/sub", directoryContent[4].FullName); + Assert.IsTrue(directoryContent[4].IsDirectory); + } + } + finally + { + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory))) + { + command.Execute(); + } + } + } + } + + [TestMethod] + public void Sftp_ChangeDirectory_DirectoryDoesNotExist() + { + const string remoteDirectory = "/home/sshnet/test123"; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory))) + { + command.Execute(); + } + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + try + { + client.ChangeDirectory(remoteDirectory); + Assert.Fail(); + } + catch (SftpPathNotFoundException) + { + } + } + } + + [TestMethod] + public void Sftp_ChangeDirectory_DirectoryExists() + { + const string remoteDirectory = "/home/sshnet/test123"; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory))) + { + command.Execute(); + } + + using (var command = client.CreateCommand("mkdir -p " + _remotePathTransformation.Transform(remoteDirectory))) + { + command.Execute(); + } + } + + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + client.ChangeDirectory(remoteDirectory); + + Assert.AreEqual(remoteDirectory, client.WorkingDirectory); + + using (var uploadStream = CreateMemoryStream(100)) + { + uploadStream.Position = 0; + + client.UploadFile(uploadStream, "gert.txt"); + + uploadStream.Position = 0; + + using (var downloadStream = client.OpenRead(remoteDirectory + "/gert.txt")) + { + Assert.AreEqual(CreateHash(uploadStream), CreateHash(downloadStream)); + } + } + } + } + finally + { + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory))) + { + command.Execute(); + } + } + } + } + + [TestMethod] + public void Sftp_DownloadFile_MemoryStream() + { + const int fileSize = 500 * 1024; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + SftpCreateRemoteFile(client, remoteFile, fileSize); + + try + { + using (var memoryStream = new MemoryStream()) + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + client.DownloadFile(remoteFile, memoryStream); + stopwatch.Stop(); + + var transferSpeed = CalculateTransferSpeed(memoryStream.Length, stopwatch.ElapsedMilliseconds); + Console.WriteLine(@"Elapsed: {0} ms", stopwatch.ElapsedMilliseconds); + Console.WriteLine(@"Transfer speed: {0:N2} KB/s", transferSpeed); + + Assert.AreEqual(fileSize, memoryStream.Length); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_SubsystemExecution_Failed() + { + var remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig(); + + // Disable SFTP subsystem + remoteSshdConfig.ClearSubsystems() + .Update() + .Restart(); + + var remoteSshdReconfiguredToDefaultState = false; + + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + try + { + client.Connect(); + Assert.Fail("Establishing SFTP connection should have failed."); + } + catch (SshException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("Subsystem 'sftp' could not be executed.", ex.Message); + } + + // Re-enable SFTP subsystem + remoteSshdConfig.Reset(); + + remoteSshdReconfiguredToDefaultState = true; + + // ensure we can reconnect the same SftpClient instance + client.Connect(); + // ensure SFTP session is correctly established + Assert.IsTrue(client.Exists(".")); + } + } + finally + { + if (!remoteSshdReconfiguredToDefaultState) + { + remoteSshdConfig.Reset(); + } + } + } + + [TestMethod] + public void Sftp_SftpFileStream_ReadAndWrite() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var s = client.Open(remoteFile, FileMode.CreateNew, FileAccess.Write)) + { + s.Write(new byte[] { 5, 4, 3, 2, 1 }, 1, 3); + } + + // switch from read to write mode + using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.ReadWrite)) + { + Assert.AreEqual(4, s.ReadByte()); + Assert.AreEqual(3, s.ReadByte()); + + Assert.AreEqual(2, s.Position); + + s.WriteByte(7); + s.Write(new byte[] { 8, 9, 10, 11, 12 }, 1, 3); + + Assert.AreEqual(6, s.Position); + } + + using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.Read)) + { + Assert.AreEqual(6, s.Length); + + var buffer = new byte[s.Length]; + Assert.AreEqual(6, s.Read(buffer, offset: 0, buffer.Length)); + + CollectionAssert.AreEqual(new byte[] { 4, 3, 7, 9, 10, 11 }, buffer); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, s.ReadByte()); + } + + // switch from read to write mode, and back to read mode and finally + // append a byte + using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.ReadWrite)) + { + Assert.AreEqual(4, s.ReadByte()); + Assert.AreEqual(3, s.ReadByte()); + Assert.AreEqual(7, s.ReadByte()); + + s.Write(new byte[] { 0, 1, 6, 4 }, 1, 2); + + Assert.AreEqual(11, s.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, s.ReadByte()); + + s.WriteByte(12); + } + + // switch from write to read mode, and back to write mode + using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.ReadWrite)) + { + s.WriteByte(5); + Assert.AreEqual(3, s.ReadByte()); + s.WriteByte(13); + + Assert.AreEqual(3, s.Position); + } + + using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.Read)) + { + Assert.AreEqual(7, s.Length); + + var buffer = new byte[s.Length]; + Assert.AreEqual(7, s.Read(buffer, offset: 0, buffer.Length)); + + CollectionAssert.AreEqual(new byte[] { 5, 3, 13, 1, 6, 11, 12 }, buffer); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, s.ReadByte()); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_SftpFileStream_SetLength_ReduceLength() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var s = client.Open(remoteFile, FileMode.CreateNew, FileAccess.Write)) + { + s.Write(new byte[] { 5, 4, 3, 2, 1 }, 1, 3); + } + + // reduce length while in write mode, with data in write buffer, and before + // current position + using (var s = client.Open(remoteFile, FileMode.Append, FileAccess.Write)) + { + s.Position = 3; + s.Write(new byte[] { 6, 7, 8, 9 }, offset: 0, count: 4); + + Assert.AreEqual(7, s.Position); + + // verify buffer has not yet been flushed + using (var fs = client.Open(remoteFile, FileMode.Open, FileAccess.Read)) + { + Assert.AreEqual(4, fs.ReadByte()); + Assert.AreEqual(3, fs.ReadByte()); + Assert.AreEqual(2, fs.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + s.SetLength(5); + + Assert.AreEqual(5, s.Position); + + // verify that buffer was flushed and size has been modified + using (var fs = client.Open(remoteFile, FileMode.Open, FileAccess.Read)) + { + Assert.AreEqual(4, fs.ReadByte()); + Assert.AreEqual(3, fs.ReadByte()); + Assert.AreEqual(2, fs.ReadByte()); + Assert.AreEqual(6, fs.ReadByte()); + Assert.AreEqual(7, fs.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + s.WriteByte(1); + } + + // verify that last byte was correctly written to the file + using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.Read)) + { + Assert.AreEqual(6, s.Length); + + var buffer = new byte[s.Length + 2]; + Assert.AreEqual(6, s.Read(buffer, offset: 0, buffer.Length)); + + CollectionAssert.AreEqual(new byte[] { 4, 3, 2, 6, 7, 1, 0, 0 }, buffer); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, s.ReadByte()); + } + + // reduce length while in read mode, but beyond current position + using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.ReadWrite)) + { + var buffer = new byte[1]; + Assert.AreEqual(1, s.Read(buffer, offset: 0, buffer.Length)); + + CollectionAssert.AreEqual(new byte[] { 4 }, buffer); + + s.SetLength(3); + + using (var w = client.Open(remoteFile, FileMode.Open, FileAccess.Write)) + { + w.Write(new byte[] { 8, 1, 6, 2 }, offset: 0, count: 4); + } + + // verify that position was not changed + Assert.AreEqual(1, s.Position); + + // verify that read buffer was cleared + Assert.AreEqual(1, s.ReadByte()); + Assert.AreEqual(6, s.ReadByte()); + Assert.AreEqual(2, s.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, s.ReadByte()); + + Assert.AreEqual(4, s.Length); + } + + // reduce length while in read mode, but before current position + using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.ReadWrite)) + { + var buffer = new byte[4]; + Assert.AreEqual(4, s.Read(buffer, offset: 0, buffer.Length)); + + CollectionAssert.AreEqual(new byte[] { 8, 1, 6, 2 }, buffer); + + Assert.AreEqual(4, s.Position); + + s.SetLength(3); + + // verify that position was moved to last byte + Assert.AreEqual(3, s.Position); + + using (var w = client.Open(remoteFile, FileMode.Open, FileAccess.Read)) + { + Assert.AreEqual(3, w.Length); + + Assert.AreEqual(8, w.ReadByte()); + Assert.AreEqual(1, w.ReadByte()); + Assert.AreEqual(6, w.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, w.ReadByte()); + } + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, s.ReadByte()); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_SftpFileStream_Seek_BeyondEndOfFile_SeekOriginBegin() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.BufferSize = 500; + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + // seek beyond EOF but not beyond buffer size + // do not write anything + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(offset: 3L, SeekOrigin.Begin); + + Assert.AreEqual(3, newPosition); + Assert.AreEqual(3, fs.Position); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(1, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + // seek beyond EOF and beyond buffer size + // do not write anything + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(offset: 700L, SeekOrigin.Begin); + + Assert.AreEqual(700, newPosition); + Assert.AreEqual(700, fs.Position); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(1, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + // seek beyond EOF but not beyond buffer size + // write less bytes than buffer size + var seekOffset = 3L; + + // buffer holding the data that we'll write to the file + var writeBuffer = GenerateRandom(size: 7); + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(seekOffset, SeekOrigin.Begin); + + Assert.AreEqual(seekOffset, newPosition); + Assert.AreEqual(seekOffset, fs.Position); + + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(seekOffset + writeBuffer.Length, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + var soughtOverReadBufferffer = new byte[seekOffset - 1]; + Assert.AreEqual(soughtOverReadBufferffer.Length, fs.Read(soughtOverReadBufferffer, offset: 0, soughtOverReadBufferffer.Length)); + Assert.IsTrue(new byte[soughtOverReadBufferffer.Length].IsEqualTo(soughtOverReadBufferffer)); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + // seek beyond EOF and beyond buffer size + // write less bytes than buffer size + seekOffset = 700L; + + // buffer holding the data that we'll write to the file + writeBuffer = GenerateRandom(size: 4); + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(seekOffset, SeekOrigin.Begin); + + Assert.AreEqual(seekOffset, newPosition); + Assert.AreEqual(seekOffset, fs.Position); + + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(seekOffset + writeBuffer.Length, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + var soughtOverReadBufferffer = new byte[seekOffset - 1]; + Assert.AreEqual(soughtOverReadBufferffer.Length, fs.Read(soughtOverReadBufferffer, offset: 0, soughtOverReadBufferffer.Length)); + Assert.IsTrue(new byte[soughtOverReadBufferffer.Length].IsEqualTo(soughtOverReadBufferffer)); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + // seek beyond EOF but not beyond buffer size + // write more bytes than buffer size + writeBuffer = GenerateRandom(size: 600); + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(offset: 3L, SeekOrigin.Begin); + + Assert.AreEqual(3, newPosition); + Assert.AreEqual(3, fs.Position); + + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(3 + writeBuffer.Length, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + Assert.AreEqual(0x00, fs.ReadByte()); + Assert.AreEqual(0x00, fs.ReadByte()); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + // seek beyond EOF and beyond buffer size + // write more bytes than buffer size + writeBuffer = GenerateRandom(size: 600); + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(offset: 550, SeekOrigin.Begin); + + Assert.AreEqual(550, newPosition); + Assert.AreEqual(550, fs.Position); + + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(550 + writeBuffer.Length, fs.Length); + + Assert.AreEqual(0x04, fs.ReadByte()); + + var soughtOverReadBuffer = new byte[550 - 1]; + Assert.AreEqual(550 - 1, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length)); + Assert.IsTrue(new byte[550 - 1].IsEqualTo(soughtOverReadBuffer)); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_SftpFileStream_Seek_BeyondEndOfFile_SeekOriginEnd() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.BufferSize = 500; + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + // seek beyond EOF but not beyond buffer size + // do not write anything + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(offset: 3L, SeekOrigin.End); + + Assert.AreEqual(4, newPosition); + Assert.AreEqual(4, fs.Position); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(1, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + // seek beyond EOF and beyond buffer size + // do not write anything + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(offset: 700L, SeekOrigin.End); + + Assert.AreEqual(701, newPosition); + Assert.AreEqual(701, fs.Position); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(1, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + // seek beyond EOF but not beyond buffer size + // write less bytes than buffer size + var seekOffset = 3L; + + // buffer holding the data that we'll write to the file + var writeBuffer = GenerateRandom(size: 7); + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(seekOffset, SeekOrigin.End); + + Assert.AreEqual(4, newPosition); + Assert.AreEqual(4, fs.Position); + + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(1 + seekOffset + writeBuffer.Length, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + var soughtOverReadBuffer = new byte[seekOffset]; + Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length)); + Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer)); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + // seek beyond EOF and beyond buffer size + // write less bytes than buffer size + seekOffset = 700L; + + // buffer holding the data that we'll write to the file + writeBuffer = GenerateRandom(size: 4); + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(seekOffset, SeekOrigin.End); + + Assert.AreEqual(1 + seekOffset, newPosition); + Assert.AreEqual(1 + seekOffset, fs.Position); + + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(1 + seekOffset + writeBuffer.Length, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + var soughtOverReadBuffer = new byte[seekOffset]; + Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length)); + Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer)); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + // seek beyond EOF but not beyond buffer size + // write more bytes than buffer size + seekOffset = 3L; + writeBuffer = GenerateRandom(size: 600); + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(seekOffset, SeekOrigin.End); + + Assert.AreEqual(1 + seekOffset, newPosition); + Assert.AreEqual(1 + seekOffset, fs.Position); + + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(1 + seekOffset + writeBuffer.Length, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + var soughtOverReadBuffer = new byte[seekOffset]; + Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length)); + Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer)); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + // seek beyond EOF and beyond buffer size + // write more bytes than buffer size + seekOffset = 550L; + writeBuffer = GenerateRandom(size: 600); + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(seekOffset, SeekOrigin.End); + + Assert.AreEqual(1 + seekOffset, newPosition); + Assert.AreEqual(1 + seekOffset, fs.Position); + + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(1 + seekOffset + writeBuffer.Length, fs.Length); + + Assert.AreEqual(0x04, fs.ReadByte()); + + var soughtOverReadBuffer = new byte[seekOffset]; + Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length)); + Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer)); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_SftpFileStream_Seek_NegativeOffSet_SeekOriginEnd() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.BufferSize = 500; + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + fs.WriteByte(0x07); + fs.WriteByte(0x05); + } + + // seek within file and not beyond buffer size + // do not write anything + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(offset: -2L, SeekOrigin.End); + + Assert.AreEqual(1, newPosition); + Assert.AreEqual(1, fs.Position); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(3, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + Assert.AreEqual(0x07, fs.ReadByte()); + Assert.AreEqual(0x05, fs.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // buffer holding the data that we'll write to the file + var writeBuffer = GenerateRandom(size: (int) client.BufferSize + 200); + + using (var fs = client.OpenWrite(remoteFile)) + { + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + // seek within EOF and beyond buffer size + // do not write anything + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(offset: -100L, SeekOrigin.End); + + Assert.AreEqual(600, newPosition); + Assert.AreEqual(600, fs.Position); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(writeBuffer.Length, fs.Length); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // seek within EOF and within buffer size + // write less bytes than buffer size + using (var fs = client.OpenWrite(remoteFile)) + { + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + + var newPosition = fs.Seek(offset: -3, SeekOrigin.End); + + Assert.AreEqual(697, newPosition); + Assert.AreEqual(697, fs.Position); + + fs.WriteByte(0x01); + fs.WriteByte(0x05); + fs.WriteByte(0x04); + fs.WriteByte(0x07); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(writeBuffer.Length + 1, fs.Length); + + var readBuffer = new byte[writeBuffer.Length - 3]; + Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(readBuffer.SequenceEqual(writeBuffer.Take(readBuffer.Length))); + + Assert.AreEqual(0x01, fs.ReadByte()); + Assert.AreEqual(0x05, fs.ReadByte()); + Assert.AreEqual(0x04, fs.ReadByte()); + Assert.AreEqual(0x07, fs.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + // buffer holding the data that we'll write to the file + writeBuffer = GenerateRandom(size: (int) client.BufferSize * 4); + + // seek within EOF and beyond buffer size + // write less bytes than buffer size + using (var fs = client.OpenWrite(remoteFile)) + { + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + + var newPosition = fs.Seek(offset: -(client.BufferSize * 2), SeekOrigin.End); + + Assert.AreEqual(1000, newPosition); + Assert.AreEqual(1000, fs.Position); + + fs.WriteByte(0x01); + fs.WriteByte(0x05); + fs.WriteByte(0x04); + fs.WriteByte(0x07); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(writeBuffer.Length, fs.Length); + + // First part of file should not have been touched + var readBuffer = new byte[(int) client.BufferSize * 2]; + Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(readBuffer.SequenceEqual(writeBuffer.Take(readBuffer.Length))); + + // Check part that should have been updated + Assert.AreEqual(0x01, fs.ReadByte()); + Assert.AreEqual(0x05, fs.ReadByte()); + Assert.AreEqual(0x04, fs.ReadByte()); + Assert.AreEqual(0x07, fs.ReadByte()); + + // Remaining bytes should not have been touched + readBuffer = new byte[((int) client.BufferSize * 2) - 4]; + Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(readBuffer.SequenceEqual(writeBuffer.Skip(((int)client.BufferSize * 2) + 4).Take(readBuffer.Length))); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + /// https://github.com/sshnet/SSH.NET/issues/253 + [TestMethod] + public void Sftp_SftpFileStream_Seek_Issue253() + { + var buf = Encoding.UTF8.GetBytes("123456"); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var ws = client.OpenWrite(remoteFile)) + { + ws.Write(buf, offset: 0, count: 3); + } + + using (var ws = client.OpenWrite(remoteFile)) + { + var newPosition = ws.Seek(offset: 3, SeekOrigin.Begin); + + Assert.AreEqual(3, newPosition); + Assert.AreEqual(3, ws.Position); + + ws.Write(buf, 3, 3); + } + + var actual = client.ReadAllText(remoteFile, Encoding.UTF8); + Assert.AreEqual("123456", actual); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_SftpFileStream_Seek_WithinReadBuffer() + { + var originalContent = GenerateRandom(size: 800); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.BufferSize = 500; + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var fs = client.OpenWrite(remoteFile)) + { + fs.Write(originalContent, offset: 0, originalContent.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + var readBuffer = new byte[200]; + + Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + + var newPosition = fs.Seek(offset: 3L, SeekOrigin.Begin); + + Assert.AreEqual(3L, newPosition); + Assert.AreEqual(3L, fs.Position); + } + + client.DeleteFile(remoteFile); + + #region Seek beyond EOF and beyond buffer size do not write anything + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(1, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(offset: 700L, SeekOrigin.Begin); + + Assert.AreEqual(700L, newPosition); + Assert.AreEqual(700L, fs.Position); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(1, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + #endregion Seek beyond EOF and beyond buffer size do not write anything + + #region Seek beyond EOF but not beyond buffer size and write less bytes than buffer size + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + var seekOffset = 3L; + var writeBuffer = GenerateRandom(size: 7); + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(seekOffset, SeekOrigin.Begin); + + Assert.AreEqual(seekOffset, newPosition); + Assert.AreEqual(seekOffset, fs.Position); + + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(seekOffset + writeBuffer.Length, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + var soughtOverReadBuffer = new byte[seekOffset - 1]; + Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length)); + Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer)); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + #endregion Seek beyond EOF but not beyond buffer size and write less bytes than buffer size + + #region Seek beyond EOF and beyond buffer size and write less bytes than buffer size + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + seekOffset = 700L; + writeBuffer = GenerateRandom(size: 4); + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(seekOffset, SeekOrigin.Begin); + + Assert.AreEqual(seekOffset, newPosition); + Assert.AreEqual(seekOffset, fs.Position); + + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(seekOffset + writeBuffer.Length, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + + var soughtOverReadBufferffer = new byte[seekOffset - 1]; + Assert.AreEqual(soughtOverReadBufferffer.Length, fs.Read(soughtOverReadBufferffer, offset: 0, soughtOverReadBufferffer.Length)); + Assert.IsTrue(new byte[soughtOverReadBufferffer.Length].IsEqualTo(soughtOverReadBufferffer)); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + #endregion Seek beyond EOF and beyond buffer size and write less bytes than buffer size + + #region Seek beyond EOF but not beyond buffer size and write more bytes than buffer size + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + seekOffset = 3L; + writeBuffer = GenerateRandom(size: 600); + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(seekOffset, SeekOrigin.Begin); + + Assert.AreEqual(seekOffset, newPosition); + Assert.AreEqual(seekOffset, fs.Position); + + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(seekOffset + writeBuffer.Length, fs.Length); + Assert.AreEqual(0x04, fs.ReadByte()); + Assert.AreEqual(0x00, fs.ReadByte()); + Assert.AreEqual(0x00, fs.ReadByte()); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + #endregion Seek beyond EOF but not beyond buffer size and write more bytes than buffer size + + #region Seek beyond EOF and beyond buffer size and write more bytes than buffer size + + // create single-byte file + using (var fs = client.OpenWrite(remoteFile)) + { + fs.WriteByte(0x04); + } + + seekOffset = 550L; + writeBuffer = GenerateRandom(size: 600); + + using (var fs = client.OpenWrite(remoteFile)) + { + var newPosition = fs.Seek(seekOffset, SeekOrigin.Begin); + + Assert.AreEqual(seekOffset, newPosition); + Assert.AreEqual(seekOffset, fs.Position); + + fs.Write(writeBuffer, offset: 0, writeBuffer.Length); + } + + using (var fs = client.OpenRead(remoteFile)) + { + Assert.AreEqual(seekOffset + writeBuffer.Length, fs.Length); + + Assert.AreEqual(0x04, fs.ReadByte()); + + var soughtOverReadBufferffer = new byte[seekOffset - 1]; + Assert.AreEqual(seekOffset - 1, fs.Read(soughtOverReadBufferffer, offset: 0, soughtOverReadBufferffer.Length)); + Assert.IsTrue(new byte[seekOffset - 1].IsEqualTo(soughtOverReadBufferffer)); + + var readBuffer = new byte[writeBuffer.Length]; + Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length)); + Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer)); + + // Ensure we've reached end of the stream + Assert.AreEqual(-1, fs.ReadByte()); + } + + client.DeleteFile(remoteFile); + + #endregion Seek beyond EOF and beyond buffer size and write more bytes than buffer size + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_SftpFileStream_SetLength_FileDoesNotExist() + { + var size = new Random().Next(500, 5000); + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + using (var s = client.Open(remoteFile, FileMode.Append, FileAccess.Write)) + { + s.SetLength(size); + } + + Assert.IsTrue(client.Exists(remoteFile)); + + var attributes = client.GetAttributes(remoteFile); + Assert.IsTrue(attributes.IsRegularFile); + Assert.AreEqual(size, attributes.Size); + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + downloaded.Position = 0; + Assert.AreEqual(CreateHash(new byte[size]), CreateHash(downloaded)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_Append_Write_ExistingFile() + { + const int fileSize = 5 * 1024; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + using (var input = CreateMemoryStream(fileSize)) + { + input.Position = 0; + + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.UploadFile(input, remoteFile); + + using (var s = client.Open(remoteFile, FileMode.Append, FileAccess.Write)) + { + var buffer = new byte[] { 0x05, 0x0f, 0x0d, 0x0a, 0x04 }; + s.Write(buffer, offset: 0, buffer.Length); + input.Write(buffer, offset: 0, buffer.Length); + } + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + + input.Position = 0; + downloaded.Position = 0; + Assert.AreEqual(CreateHash(input), CreateHash(downloaded)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_Append_Write_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + #region Verify if merely opening the file for append creates a zero-byte file + + using (client.Open(remoteFile, FileMode.Append, FileAccess.Write)) + { + } + + Assert.IsTrue(client.Exists(remoteFile)); + + var attributes = client.GetAttributes(remoteFile); + Assert.IsTrue(attributes.IsRegularFile); + Assert.AreEqual(0L, attributes.Size); + + #endregion Verify if merely opening the file for append creates it + + client.DeleteFile(remoteFile); + + #region Verify if content is actually written to the file + + var content = GenerateRandom(size: 100); + + using (var s = client.Open(remoteFile, FileMode.Append, FileAccess.Write)) + { + s.Write(content, offset: 0, content.Length); + } + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + downloaded.Position = 0; + Assert.AreEqual(CreateHash(content), CreateHash(downloaded)); + } + + #endregion Verify if content is actually written to the file + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + + + [TestMethod] + public void Sftp_Open_PathAndMode_ModeIsCreate_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + #region Verify if merely opening the file for create creates a zero-byte file + + using (client.Open(remoteFile, FileMode.Create)) + { + } + + Assert.IsTrue(client.Exists(remoteFile)); + + var attributes = client.GetAttributes(remoteFile); + Assert.IsTrue(attributes.IsRegularFile); + Assert.AreEqual(0L, attributes.Size); + + #endregion Verify if merely opening the file for create creates a zero-byte file + + client.DeleteFile(remoteFile); + + #region Verify if content is actually written to the file + + var content = GenerateRandom(size: 100); + + using (var s = client.Open(remoteFile, FileMode.Create)) + { + s.Write(content, offset: 0, content.Length); + } + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + downloaded.Position = 0; + Assert.AreEqual(CreateHash(content), CreateHash(downloaded)); + } + + #endregion Verify if content is actually written to the file + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_PathAndMode_ModeIsCreate_ExistingFile() + { + const int fileSize = 5 * 1024; + var newContent = new byte[] { 0x07, 0x03, 0x02, 0x0b }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + using (var input = CreateMemoryStream(fileSize)) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + input.Position = 0; + client.UploadFile(input, remoteFile); + + using (var stream = client.Open(remoteFile, FileMode.Create)) + { + // Verify if merely opening the file for create overwrites the file + var attributes = client.GetAttributes(remoteFile); + Assert.IsTrue(attributes.IsRegularFile); + Assert.AreEqual(0L, attributes.Size); + + stream.Write(newContent, offset: 0, newContent.Length); + stream.Position = 0; + + Assert.AreEqual(CreateHash(newContent), CreateHash(stream)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_PathAndModeAndAccess_ModeIsCreate_AccessIsReadWrite_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + #region Verify if merely opening the file for create creates a zero-byte file + + using (client.Open(remoteFile, FileMode.Create, FileAccess.ReadWrite)) + { + } + + Assert.IsTrue(client.Exists(remoteFile)); + + var attributes = client.GetAttributes(remoteFile); + Assert.IsTrue(attributes.IsRegularFile); + Assert.AreEqual(0L, attributes.Size); + + #endregion Verify if merely opening the file for create creates a zero-byte file + + client.DeleteFile(remoteFile); + + #region Verify if content is actually written to the file + + var content = GenerateRandom(size: 100); + + using (var s = client.Open(remoteFile, FileMode.Create, FileAccess.ReadWrite)) + { + s.Write(content, offset: 0, content.Length); + } + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + downloaded.Position = 0; + Assert.AreEqual(CreateHash(content), CreateHash(downloaded)); + } + + #endregion Verify if content is actually written to the file + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_PathAndModeAndAccess_ModeIsCreate_AccessIsReadWrite_ExistingFile() + { + const int fileSize = 5 * 1024; + var newContent = new byte[] { 0x07, 0x03, 0x02, 0x0b }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + using (var input = CreateMemoryStream(fileSize)) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + input.Position = 0; + client.UploadFile(input, remoteFile); + + using (var stream = client.Open(remoteFile, FileMode.Create, FileAccess.ReadWrite)) + { + // Verify if merely opening the file for create overwrites the file + var attributes = client.GetAttributes(remoteFile); + Assert.IsTrue(attributes.IsRegularFile); + Assert.AreEqual(0L, attributes.Size); + + stream.Write(newContent, offset: 0, newContent.Length); + stream.Position = 0; + + Assert.AreEqual(CreateHash(newContent), CreateHash(stream)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_PathAndModeAndAccess_ModeIsCreate_AccessIsWrite_ExistingFile() + { + // use new content that contains less bytes than original content to + // verify whether file is first truncated + var originalContent = new byte[] { 0x05, 0x0f, 0x0d, 0x0a, 0x04 }; + var newContent = new byte[] { 0x07, 0x03, 0x02, 0x0b }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllBytes(remoteFile, originalContent); + + using (var s = client.Open(remoteFile, FileMode.Create, FileAccess.Write)) + { + s.Write(newContent, offset: 0, newContent.Length); + } + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + + downloaded.Position = 0; + Assert.AreEqual(CreateHash(newContent), CreateHash(downloaded)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_PathAndModeAndAccess_ModeIsCreate_AccessIsWrite_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + #region Verify if merely opening the file for create creates a zero-byte file + + using (client.Open(remoteFile, FileMode.Create, FileAccess.Write)) + { + } + + Assert.IsTrue(client.Exists(remoteFile)); + + var attributes = client.GetAttributes(remoteFile); + Assert.IsTrue(attributes.IsRegularFile); + Assert.AreEqual(0L, attributes.Size); + + #endregion Verify if merely opening the file for create creates a zero-byte file + + client.DeleteFile(remoteFile); + + #region Verify if content is actually written to the file + + var content = GenerateRandom(size: 100); + + using (var s = client.Open(remoteFile, FileMode.Create, FileAccess.Write)) + { + s.Write(content, offset: 0, content.Length); + } + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + downloaded.Position = 0; + Assert.AreEqual(CreateHash(content), CreateHash(downloaded)); + } + + #endregion Verify if content is actually written to the file + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_CreateNew_Write_ExistingFile() + { + const int fileSize = 5 * 1024; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + using (var input = CreateMemoryStream(fileSize)) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + input.Position = 0; + + try + { + client.UploadFile(input, remoteFile); + + Stream stream = null; + + try + { + stream = client.Open(remoteFile, FileMode.CreateNew, FileAccess.Write); + Assert.Fail(); + } + catch (SshException) + { + } + finally + { + stream?.Dispose(); + } + + // Verify that the file was not modified + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + + input.Position = 0; + downloaded.Position = 0; + Assert.AreEqual(CreateHash(input), CreateHash(downloaded)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_CreateNew_Write_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + #region Verify if merely opening the file creates a zero-byte file + + using (client.Open(remoteFile, FileMode.CreateNew, FileAccess.Write)) + { + } + + Assert.IsTrue(client.Exists(remoteFile)); + + var attributes = client.GetAttributes(remoteFile); + Assert.IsTrue(attributes.IsRegularFile); + Assert.AreEqual(0L, attributes.Size); + + #endregion Verify if merely opening the file creates it + + client.DeleteFile(remoteFile); + + #region Verify if content is actually written to the file + + var content = GenerateRandom(size: 100); + + using (var s = client.Open(remoteFile, FileMode.CreateNew, FileAccess.Write)) + { + s.Write(content, offset: 0, content.Length); + } + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + downloaded.Position = 0; + Assert.AreEqual(CreateHash(content), CreateHash(downloaded)); + } + + #endregion Verify if content is actually written to the file + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_Open_Write_ExistingFile() + { + // use new content that contains less bytes than original content to + // verify whether file is first truncated + var originalContent = new byte[] { 0x05, 0x0f, 0x0d, 0x0a, 0x04 }; + var newContent = new byte[] { 0x07, 0x03, 0x02, 0x0b }; + var expectedContent = new byte[] { 0x07, 0x03, 0x02, 0x0b, 0x04 }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllBytes(remoteFile, originalContent); + + using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.Write)) + { + s.Write(newContent, offset: 0, newContent.Length); + } + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + + downloaded.Position = 0; + Assert.AreEqual(CreateHash(expectedContent), CreateHash(downloaded)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_Open_Write_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + Stream stream = null; + + try + { + stream = client.Open(remoteFile, FileMode.Open, FileAccess.Write); + Assert.Fail(); + } + catch (SshException) + { + } + finally + { + stream?.Dispose(); + } + + Assert.IsFalse(client.Exists(remoteFile)); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_OpenOrCreate_Write_ExistingFile() + { + // use new content that contains less bytes than original content to + // verify whether file is first truncated + var originalContent = new byte[] { 0x05, 0x0f, 0x0d, 0x0a, 0x04 }; + var newContent = new byte[] { 0x07, 0x03, 0x02, 0x0b }; + var expectedContent = new byte[] { 0x07, 0x03, 0x02, 0x0b, 0x04 }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + client.WriteAllBytes(remoteFile, originalContent); + + using (var s = client.Open(remoteFile, FileMode.OpenOrCreate, FileAccess.Write)) + { + s.Write(newContent, offset: 0, newContent.Length); + } + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + + downloaded.Position = 0; + Assert.AreEqual(CreateHash(expectedContent), CreateHash(downloaded)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_OpenOrCreate_Write_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + #region Verify if merely opening the file creates a zero-byte file + + using (client.Open(remoteFile, FileMode.OpenOrCreate, FileAccess.Write)) + { + } + + Assert.IsTrue(client.Exists(remoteFile)); + + var attributes = client.GetAttributes(remoteFile); + Assert.IsTrue(attributes.IsRegularFile); + Assert.AreEqual(0L, attributes.Size); + + #endregion Verify if merely opening the file creates it + + client.DeleteFile(remoteFile); + + #region Verify if content is actually written to the file + + var content = GenerateRandom(size: 100); + + using (var s = client.Open(remoteFile, FileMode.OpenOrCreate, FileAccess.Write)) + { + s.Write(content, offset: 0, content.Length); + } + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + downloaded.Position = 0; + Assert.AreEqual(CreateHash(content), CreateHash(downloaded)); + } + + #endregion Verify if content is actually written to the file + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + + + + + + + [TestMethod] + public void Sftp_Open_Truncate_Write_ExistingFile() + { + const int fileSize = 5 * 1024; + + // use new content that contains less bytes than original content to + // verify whether file is first truncated + var originalContent = new byte[] { 0x05, 0x0f, 0x0d, 0x0a, 0x04 }; + var newContent = new byte[] { 0x07, 0x03, 0x02, 0x0b }; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + using (var input = CreateMemoryStream(fileSize)) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + input.Position = 0; + + try + { + client.WriteAllBytes(remoteFile, originalContent); + + using (var s = client.Open(remoteFile, FileMode.Truncate, FileAccess.Write)) + { + s.Write(newContent, offset: 0, newContent.Length); + } + + using (var downloaded = new MemoryStream()) + { + client.DownloadFile(remoteFile, downloaded); + + input.Position = 0; + downloaded.Position = 0; + Assert.AreEqual(CreateHash(newContent), CreateHash(downloaded)); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_Open_Truncate_Write_FileDoesNotExist() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + try + { + Stream stream = null; + + try + { + stream = client.Open(remoteFile, FileMode.Truncate, FileAccess.Write); + Assert.Fail(); + } + catch (SshException) + { + } + + Assert.IsFalse(client.Exists(remoteFile)); + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + [TestMethod] + public void Sftp_OpenRead() + { + const int fileSize = 5 * 1024 * 1024; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var remoteFile = GenerateUniqueRemoteFileName(); + + SftpCreateRemoteFile(client, remoteFile, fileSize); + + try + { + using (var s = client.OpenRead(remoteFile)) + { + var buffer = new byte[s.Length]; + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + var bytesRead = s.Read(buffer, offset: 0, buffer.Length); + + stopwatch.Stop(); + + var transferSpeed = CalculateTransferSpeed(bytesRead, stopwatch.ElapsedMilliseconds); + Console.WriteLine(@"Elapsed: {0} ms", stopwatch.ElapsedMilliseconds); + Console.WriteLine(@"Transfer speed: {0:N2} KB/s", transferSpeed); + + Assert.AreEqual(fileSize, bytesRead); + } + } + finally + { + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + } + } + } + + private static IEnumerable GetSftpUploadFileFileStreamData() + { + yield return new object[] { 0 }; + yield return new object[] { 5 * 1024 * 1024 }; + } + + private static Encoding GetRandomEncoding() + { + var random = new Random().Next(1, 3); + switch (random) + { + case 1: + return Encoding.Unicode; + case 2: + return Encoding.UTF8; + case 3: + return Encoding.UTF32; + default: + throw new NotImplementedException(); + } + } + + private static byte[] GetBytesWithPreamble(string text, Encoding encoding) + { + var preamble = encoding.GetPreamble(); + var textBytes = encoding.GetBytes(text); + + if (preamble.Length != 0) + { + var textAndPreambleBytes = new byte[preamble.Length + textBytes.Length]; + Buffer.BlockCopy(preamble, srcOffset: 0, textAndPreambleBytes, dstOffset: 0, preamble.Length); + Buffer.BlockCopy(textBytes, srcOffset: 0, textAndPreambleBytes, preamble.Length, textBytes.Length); + return textAndPreambleBytes; + } + + return textBytes; + } + + private static Stream GetResourceStream(string resourceName) + { + var type = typeof(SftpTests); + var resourceStream = type.Assembly.GetManifestResourceStream(resourceName); + if (resourceStream == null) + { + throw new ArgumentException($"Resource '{resourceName}' not found in assembly '{type.Assembly.FullName}'.", nameof(resourceName)); + } + return resourceStream; + } + + private static decimal CalculateTransferSpeed(long length, long elapsedMilliseconds) + { + return (length / 1024m) / (elapsedMilliseconds / 1000m); + } + + private static void SftpCreateRemoteFile(SftpClient client, string remoteFile, int size) + { + var file = CreateTempFile(size); + + try + { + using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + client.UploadFile(fs, remoteFile); + } + } + finally + { + File.Delete(file); + } + } + + private static byte[] GenerateRandom(int size) + { + var random = new Random(); + var randomContent = new byte[size]; + random.NextBytes(randomContent); + return randomContent; + } + + private static Stream CreateStreamWithContent(string content) + { + var memoryStream = new MemoryStream(); + var sw = new StreamWriter(memoryStream, Encoding.ASCII, 1024); + sw.Write(content); + sw.Flush(); + memoryStream.Position = 0; + return memoryStream; + } + + private static string GenerateUniqueRemoteFileName() + { + return $"/home/sshnet/{Guid.NewGuid():D}"; + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/SshClientTests.cs b/src/Renci.SshNet.IntegrationTests/SshClientTests.cs index 867514441..b737b343f 100644 --- a/src/Renci.SshNet.IntegrationTests/SshClientTests.cs +++ b/src/Renci.SshNet.IntegrationTests/SshClientTests.cs @@ -1,6 +1,4 @@ -using Renci.SshNet; - -namespace IntegrationTests +namespace Renci.SshNet.IntegrationTests { /// /// The SSH client integration tests diff --git a/src/Renci.SshNet.IntegrationTests/SshConnectionDisruptor.cs b/src/Renci.SshNet.IntegrationTests/SshConnectionDisruptor.cs new file mode 100644 index 000000000..741134114 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/SshConnectionDisruptor.cs @@ -0,0 +1,41 @@ +namespace Renci.SshNet.IntegrationTests +{ + internal class SshConnectionDisruptor + { + private readonly IConnectionInfoFactory _connectionInfoFactory; + + public SshConnectionDisruptor(IConnectionInfoFactory connectionInfoFactory) + { + _connectionInfoFactory = connectionInfoFactory; + } + + public SshConnectionRestorer BreakConnections() + { + var client = new SshClient(_connectionInfoFactory.Create()); + + client.Connect(); + + PauseSshd(client); + + return new SshConnectionRestorer(client); + } + + private static void PauseSshd(SshClient client) + { + var command = client.CreateCommand("sudo echo 'DenyUsers sshnet' >> /etc/ssh/sshd_config"); + var output = command.Execute(); + if (command.ExitStatus != 0) + { + throw new ApplicationException( + $"Blocking user sshnet failed with exit code {command.ExitStatus}.\r\n{output}\r\n{command.Error}"); + } + command = client.CreateCommand("sudo pkill -9 -U sshnet -f sshd.pam"); + output = command.Execute(); + if (command.ExitStatus != 0) + { + throw new ApplicationException( + $"Killing sshd.pam service failed with exit code {command.ExitStatus}.\r\n{output}\r\n{command.Error}"); + } + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/SshConnectionRestorer.cs b/src/Renci.SshNet.IntegrationTests/SshConnectionRestorer.cs new file mode 100644 index 000000000..d7c6437db --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/SshConnectionRestorer.cs @@ -0,0 +1,36 @@ +namespace Renci.SshNet.IntegrationTests +{ + internal class SshConnectionRestorer : IDisposable + { + private SshClient _sshClient; + + public SshConnectionRestorer(SshClient sshClient) + { + _sshClient = sshClient; + } + + public void RestoreConnections() + { + var command = _sshClient.CreateCommand("sudo sed -i '/DenyUsers sshnet/d' /etc/ssh/sshd_config"); + var output = command.Execute(); + if (command.ExitStatus != 0) + { + throw new ApplicationException( + $"Unblocking user sshnet failed with exit code {command.ExitStatus}.\r\n{output}\r\n{command.Error}"); + } + command = _sshClient.CreateCommand("sudo /usr/sbin/sshd.pam"); + output = command.Execute(); + if (command.ExitStatus != 0) + { + throw new ApplicationException( + $"Resuming ssh service failed with exit code {command.ExitStatus}.\r\n{output}\r\n{command.Error}"); + } + } + + public void Dispose() + { + _sshClient?.Dispose(); + _sshClient = null; + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/SshTests.cs b/src/Renci.SshNet.IntegrationTests/SshTests.cs new file mode 100644 index 000000000..5f3e58984 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/SshTests.cs @@ -0,0 +1,972 @@ +using System.ComponentModel; +using System.Net; +using System.Net.Sockets; + +using Renci.SshNet.Common; +using Renci.SshNet.IntegrationTests.Common; + +namespace Renci.SshNet.IntegrationTests +{ + [TestClass] + public class SshTests : TestBase + { + private IConnectionInfoFactory _connectionInfoFactory; + private IConnectionInfoFactory _adminConnectionInfoFactory; + private RemoteSshdConfig _remoteSshdConfig; + + [TestInitialize] + public void SetUp() + { + _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort); + _adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort); + + _remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig(); + _remoteSshdConfig.AllowTcpForwarding() + .PrintMotd(false) + .Update() + .Restart(); + } + + [TestCleanup] + public void TearDown() + { + _remoteSshdConfig?.Reset(); + } + + /// + /// Test for a channel that is being closed by the server. + /// + [TestMethod] + public void Ssh_ShellStream_Exit() + { + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var terminalModes = new Dictionary + { + { TerminalModes.ECHO, 0 } + }; + + using (var shellStream = client.CreateShellStream("xterm", 80, 24, 800, 600, 1024, terminalModes)) + { + shellStream.WriteLine("echo Hello!"); + shellStream.WriteLine("exit"); + + Thread.Sleep(1000); + + try + { + shellStream.Write("ABC"); + Assert.Fail(); + } + catch (ObjectDisposedException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("ShellStream", ex.ObjectName); + Assert.AreEqual($"Cannot access a disposed object.{Environment.NewLine}Object name: '{ex.ObjectName}'.", ex.Message); + } + + var line = shellStream.ReadLine(); + Assert.IsNotNull(line); + Assert.IsTrue(line.EndsWith("Hello!"), line); + + // TODO: ReadLine should return null when the buffer is empty and the channel has been closed (issue #672) + try + { + line = shellStream.ReadLine(); + Assert.Fail(line); + } + catch (NullReferenceException) + { + + } + } + } + } + + /// + /// https://github.com/sshnet/SSH.NET/issues/63 + /// + [TestMethod] + [Category("Reproduction Tests")] + [Ignore] + public void Ssh_ShellStream_IntermittendOutput() + { + const string remoteFile = "/home/sshnet/test.sh"; + + var expectedResult = string.Join("\n", + "Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Line 5 ", + "Line 6"); + + var scriptBuilder = new StringBuilder(); + scriptBuilder.Append("#!/bin/sh\n"); + scriptBuilder.Append("echo Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + scriptBuilder.Append("sleep .5\n"); + scriptBuilder.Append("echo Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + scriptBuilder.Append("echo Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + scriptBuilder.Append("echo Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + scriptBuilder.Append("sleep 2\n"); + scriptBuilder.Append("echo \"Line 5 \"\n"); + scriptBuilder.Append("echo Line 6 \n"); + scriptBuilder.Append("exit 13\n"); + + using (var sshClient = new SshClient(_connectionInfoFactory.Create())) + { + sshClient.Connect(); + + CreateShellScript(_connectionInfoFactory, remoteFile, scriptBuilder.ToString()); + + try + { + var terminalModes = new Dictionary + { + { TerminalModes.ECHO, 0 } + }; + + using (var shellStream = sshClient.CreateShellStream("xterm", 80, 24, 800, 600, 1024, terminalModes)) + { + shellStream.WriteLine(remoteFile); + Thread.Sleep(1200); + using (var reader = new StreamReader(shellStream, new UTF8Encoding(false), false, 10)) + { + var lines = new List(); + string line = null; + while ((line = reader.ReadLine()) != null) + { + lines.Add(line); + } + Assert.AreEqual(6, lines.Count, string.Join("\n", lines)); + Assert.AreEqual(expectedResult, string.Join("\n", lines)); + } + } + } + finally + { + RemoveFileOrDirectory(sshClient, remoteFile); + } + } + } + + /// + /// Issue 1555 + /// + [TestMethod] + public void Ssh_CreateShell() + { + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var input = new MemoryStream()) + using (var output = new MemoryStream()) + using (var extOutput = new MemoryStream()) + { + var shell = client.CreateShell(input, output, extOutput); + shell.Start(); + + var inputWriter = new StreamWriter(input, Encoding.ASCII, 1024); + inputWriter.WriteLine("echo $PATH"); + + var outputReader = new StreamReader(output, Encoding.ASCII, false, 1024); + Console.WriteLine(outputReader.ReadToEnd()); + + shell.Stop(); + } + } + } + + [TestMethod] + public void Ssh_Command_IntermittendOutput_EndExecute() + { + const string remoteFile = "/home/sshnet/test.sh"; + + var expectedResult = string.Join("\n", + "Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Line 5 ", + "Line 6", + ""); + + var scriptBuilder = new StringBuilder(); + scriptBuilder.Append("#!/bin/sh\n"); + scriptBuilder.Append("echo Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + scriptBuilder.Append("sleep .5\n"); + scriptBuilder.Append("echo Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + scriptBuilder.Append("echo Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + scriptBuilder.Append("echo Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + scriptBuilder.Append("sleep 2\n"); + scriptBuilder.Append("echo \"Line 5 \"\n"); + scriptBuilder.Append("echo Line 6 \n"); + scriptBuilder.Append("exit 13\n"); + + using (var sshClient = new SshClient(_connectionInfoFactory.Create())) + { + sshClient.Connect(); + + CreateShellScript(_connectionInfoFactory, remoteFile, scriptBuilder.ToString()); + + try + { + using (var cmd = sshClient.CreateCommand("chmod 777 " + remoteFile)) + { + cmd.Execute(); + + Assert.AreEqual(0, cmd.ExitStatus, cmd.Error); + } + + using (var command = sshClient.CreateCommand(remoteFile)) + { + var asyncResult = command.BeginExecute(); + var actualResult = command.EndExecute(asyncResult); + + Assert.AreEqual(expectedResult, actualResult); + Assert.AreEqual(expectedResult, command.Result); + Assert.AreEqual(13, command.ExitStatus); + } + } + finally + { + RemoveFileOrDirectory(sshClient, remoteFile); + } + } + } + + /// + /// Ignored for now, because: + /// * OutputStream.Read(...) does not block when no data is available + /// * SshCommand.(Begin)Execute consumes *OutputStream*, advancing its position. + /// + /// https://github.com/sshnet/SSH.NET/issues/650 + /// + [TestMethod] + [Ignore] + public void Ssh_Command_IntermittendOutput_OutputStream() + { + const string remoteFile = "/home/sshnet/test.sh"; + + var expectedResult = string.Join("\n", + "Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Line 5 ", + "Line 6"); + + var scriptBuilder = new StringBuilder(); + scriptBuilder.Append("#!/bin/sh\n"); + scriptBuilder.Append("echo Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + scriptBuilder.Append("sleep .5\n"); + scriptBuilder.Append("echo Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + scriptBuilder.Append("echo Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + scriptBuilder.Append("echo Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + scriptBuilder.Append("sleep 2\n"); + scriptBuilder.Append("echo \"Line 5 \"\n"); + scriptBuilder.Append("echo Line 6 \n"); + scriptBuilder.Append("exit 13\n"); + + using (var sshClient = new SshClient(_connectionInfoFactory.Create())) + { + sshClient.Connect(); + + CreateShellScript(_connectionInfoFactory, remoteFile, scriptBuilder.ToString()); + + try + { + using (var cmd = sshClient.CreateCommand("chmod 777 " + remoteFile)) + { + cmd.Execute(); + + Assert.AreEqual(0, cmd.ExitStatus, cmd.Error); + } + + using (var command = sshClient.CreateCommand(remoteFile)) + { + var asyncResult = command.BeginExecute(); + + using (var reader = new StreamReader(command.OutputStream, new UTF8Encoding(false), false, 10)) + { + var lines = new List(); + string line = null; + while ((line = reader.ReadLine()) != null) + { + lines.Add(line); + } + + Assert.AreEqual(6, lines.Count, string.Join("\n", lines)); + Assert.AreEqual(expectedResult, string.Join("\n", lines)); + Assert.AreEqual(13, command.ExitStatus); + } + + var actualResult = command.EndExecute(asyncResult); + + Assert.AreEqual(expectedResult, actualResult); + Assert.AreEqual(expectedResult, command.Result); + } + } + finally + { + RemoveFileOrDirectory(sshClient, remoteFile); + } + } + } + + [TestMethod] + public void Ssh_DynamicPortForwarding_DisposeSshClientWithoutStoppingPort() + { + const string searchText = "HTTP/1.1 301 Moved Permanently"; + const string hostName = "github.com"; + + var httpGetRequest = Encoding.ASCII.GetBytes($"GET / HTTP/1.1\r\nHost: {hostName}\r\n\r\n"); + Socket socksSocket; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200); + client.Connect(); + + var forwardedPort = new ForwardedPortDynamic(1080); + forwardedPort.Exception += (sender, args) => Console.WriteLine(args.Exception.ToString()); + client.AddForwardedPort(forwardedPort); + forwardedPort.Start(); + + var socksClient = new Socks5Handler(new IPEndPoint(IPAddress.Loopback, 1080), + string.Empty, + string.Empty); + + socksSocket = socksClient.Connect(hostName, 80); + socksSocket.Send(httpGetRequest); + + var httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII); + Assert.IsTrue(httpResponse.Contains(searchText), httpResponse); + } + + Assert.IsTrue(socksSocket.Connected); + + // check if client socket was properly closed + Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None)); + } + + [TestMethod] + public void Ssh_DynamicPortForwarding_DomainName() + { + const string searchText = "HTTP/1.1 301 Moved Permanently"; + const string hostName = "github.com"; + + // Set-up a host alias for google.be on the remote server that is not known locally; this allows us to + // verify whether the host name is resolved remotely. + const string hostNameAlias = "dynamicportforwarding-test.for.sshnet"; + + // Construct a HTTP request for which we expected the response to contain the search text. + var httpGetRequest = Encoding.ASCII.GetBytes($"GET / HTTP/1.1\r\nHost: {hostName}\r\n\r\n"); + + var ipAddresses = Dns.GetHostAddresses(hostName); + var hostsFileUpdated = AddOrUpdateHostsEntry(_adminConnectionInfoFactory, ipAddresses[0], hostNameAlias); + + try + { + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200); + client.Connect(); + + var forwardedPort = new ForwardedPortDynamic(1080); + forwardedPort.Exception += (sender, args) => Console.WriteLine(args.Exception.ToString()); + client.AddForwardedPort(forwardedPort); + forwardedPort.Start(); + + var socksClient = new Socks5Handler(new IPEndPoint(IPAddress.Loopback, 1080), + string.Empty, + string.Empty); + var socksSocket = socksClient.Connect(hostNameAlias, 80); + + socksSocket.Send(httpGetRequest); + var httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII); + Assert.IsTrue(httpResponse.Contains(searchText), httpResponse); + + // Verify if port is still open + socksSocket.Send(httpGetRequest); + GetHttpResponse(socksSocket, Encoding.ASCII); + + forwardedPort.Stop(); + + Assert.IsTrue(socksSocket.Connected); + + // check if client socket was properly closed + Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None)); + + forwardedPort.Start(); + + // create new SOCKS connection and very whether the forwarded port is functional again + socksSocket = socksClient.Connect(hostNameAlias, 80); + + socksSocket.Send(httpGetRequest); + httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII); + Assert.IsTrue(httpResponse.Contains(searchText), httpResponse); + + forwardedPort.Dispose(); + + Assert.IsTrue(socksSocket.Connected); + + // check if client socket was properly closed + Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None)); + + forwardedPort.Dispose(); + } + } + finally + { + if (hostsFileUpdated) + { + RemoveHostsEntry(_adminConnectionInfoFactory, ipAddresses[0], hostNameAlias); + } + } + } + + [TestMethod] + public void Ssh_DynamicPortForwarding_IPv4() + { + const string searchText = "HTTP/1.1 301 Moved Permanently"; + const string hostName = "github.com"; + + var httpGetRequest = Encoding.ASCII.GetBytes($"GET /null HTTP/1.1\r\nHost: {hostName}\r\n\r\n"); + + var ipv4 = Dns.GetHostAddresses(hostName).FirstOrDefault(p => p.AddressFamily == AddressFamily.InterNetwork); + Assert.IsNotNull(ipv4, $@"No IPv4 address found for '{hostName}'."); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200); + client.Connect(); + + var forwardedPort = new ForwardedPortDynamic(1080); + forwardedPort.Exception += (sender, args) => Console.WriteLine(args.Exception.ToString()); + client.AddForwardedPort(forwardedPort); + forwardedPort.Start(); + + var socksClient = new Socks5Handler(new IPEndPoint(IPAddress.Loopback, 1080), + string.Empty, + string.Empty); + var socksSocket = socksClient.Connect(new IPEndPoint(ipv4, 80)); + + socksSocket.Send(httpGetRequest); + var httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII); + Assert.IsTrue(httpResponse.Contains(searchText), httpResponse); + + forwardedPort.Dispose(); + + // check if client socket was properly closed + Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None)); + } + } + + /// + /// Verifies whether channels are effectively closed. + /// + [TestMethod] + public void Ssh_LocalPortForwardingCloseChannels() + { + const string hostNameAlias = "localportforwarding-test.for.sshnet"; + const string hostName = "github.com"; + + var ipAddress = Dns.GetHostAddresses(hostName)[0]; + + var hostsFileUpdated = AddOrUpdateHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias); + + try + { + var connectionInfo = _connectionInfoFactory.Create(); + connectionInfo.MaxSessions = 1; + + using (var client = new SshClient(connectionInfo)) + { + client.Connect(); + + var localEndPoint = new IPEndPoint(IPAddress.Loopback, 1225); + + for (var i = 0; i < (connectionInfo.MaxSessions + 1); i++) + { + var forwardedPort = new ForwardedPortLocal(localEndPoint.Address.ToString(), + (uint)localEndPoint.Port, + hostNameAlias, + 80); + client.AddForwardedPort(forwardedPort); + forwardedPort.Start(); + + try + { + var httpRequest = (HttpWebRequest) WebRequest.Create("http://" + localEndPoint); + httpRequest.Host = hostName; + httpRequest.Method = "GET"; + httpRequest.AllowAutoRedirect = false; + + try + { + using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse()) + { + Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode); + } + } + catch (WebException ex) + { + Assert.AreEqual(WebExceptionStatus.ProtocolError, ex.Status); + Assert.IsNotNull(ex.Response); + + using (var httpResponse = ex.Response as HttpWebResponse) + { + Assert.IsNotNull(httpResponse); + Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode); + } + } + } + finally + { + client.RemoveForwardedPort(forwardedPort); + } + } + } + } + finally + { + if (hostsFileUpdated) + { + RemoveHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias); + } + } + } + + [TestMethod] + public void Ssh_LocalPortForwarding() + { + const string hostNameAlias = "localportforwarding-test.for.sshnet"; + const string hostName = "github.com"; + + var ipAddress = Dns.GetHostAddresses(hostName)[0]; + + var hostsFileUpdated = AddOrUpdateHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias); + + try + { + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + var localEndPoint = new IPEndPoint(IPAddress.Loopback, 1225); + + var forwardedPort = new ForwardedPortLocal(localEndPoint.Address.ToString(), + (uint)localEndPoint.Port, + hostNameAlias, + 80); + forwardedPort.Exception += + (sender, args) => Console.WriteLine(@"ForwardedPort exception: " + args.Exception); + client.AddForwardedPort(forwardedPort); + forwardedPort.Start(); + + try + { + var httpRequest = (HttpWebRequest) WebRequest.Create("http://" + localEndPoint); + httpRequest.Host = hostName; + httpRequest.Method = "GET"; + httpRequest.Accept = "text/html"; + httpRequest.AllowAutoRedirect = false; + + try + { + using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse()) + { + Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode); + } + } + catch (WebException ex) + { + Assert.AreEqual(WebExceptionStatus.ProtocolError, ex.Status); + Assert.IsNotNull(ex.Response); + + using (var httpResponse = ex.Response as HttpWebResponse) + { + Assert.IsNotNull(httpResponse); + Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode); + } + } + } + finally + { + client.RemoveForwardedPort(forwardedPort); + } + } + } + finally + { + if (hostsFileUpdated) + { + RemoveHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias); + } + } + } + + [TestMethod] + public void Ssh_RemotePortForwarding() + { + var hostAddresses = Dns.GetHostAddresses(Dns.GetHostName()); + var ipv4HostAddress = hostAddresses.First(p => p.AddressFamily == AddressFamily.InterNetwork); + + var endpoint1 = new IPEndPoint(ipv4HostAddress, 666); + var endpoint2 = new IPEndPoint(ipv4HostAddress, 667); + + var bytesReceivedOnListener1 = new List(); + var bytesReceivedOnListener2 = new List(); + + using (var socketListener1 = new AsyncSocketListener(endpoint1)) + using (var socketListener2 = new AsyncSocketListener(endpoint2)) + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + socketListener1.BytesReceived += (received, socket) => bytesReceivedOnListener1.AddRange(received); + socketListener1.Start(); + + socketListener2.BytesReceived += (received, socket) => bytesReceivedOnListener2.AddRange(received); + socketListener2.Start(); + + client.Connect(); + + var forwardedPort1 = new ForwardedPortRemote(IPAddress.Loopback, + 10000, + endpoint1.Address, + (uint)endpoint1.Port); + forwardedPort1.Exception += (sender, args) => Console.WriteLine(@"forwardedPort1 exception: " + args.Exception); + client.AddForwardedPort(forwardedPort1); + forwardedPort1.Start(); + + var forwardedPort2 = new ForwardedPortRemote(IPAddress.Loopback, + 10001, + endpoint2.Address, + (uint)endpoint2.Port); + forwardedPort2.Exception += (sender, args) => Console.WriteLine(@"forwardedPort2 exception: " + args.Exception); + client.AddForwardedPort(forwardedPort2); + forwardedPort2.Start(); + + using (var s = client.CreateShellStream("a", 80, 25, 800, 600, 200)) + { + s.WriteLine($"telnet {forwardedPort1.BoundHost} {forwardedPort1.BoundPort}"); + s.Expect($"Connected to {forwardedPort1.BoundHost}\r\n"); + s.WriteLine("ABC"); + s.Flush(); + s.Expect("ABC"); + s.Close(); + } + + using (var s = client.CreateShellStream("b", 80, 25, 800, 600, 200)) + { + s.WriteLine($"telnet {forwardedPort2.BoundHost} {forwardedPort2.BoundPort}"); + s.Expect($"Connected to {forwardedPort2.BoundHost}\r\n"); + s.WriteLine("DEF"); + s.Flush(); + s.Expect("DEF"); + s.Close(); + } + + forwardedPort1.Stop(); + forwardedPort2.Stop(); + } + + var textReceivedOnListener1 = Encoding.ASCII.GetString(bytesReceivedOnListener1.ToArray()); + Assert.AreEqual("ABC\r\n", textReceivedOnListener1); + + var textReceivedOnListener2 = Encoding.ASCII.GetString(bytesReceivedOnListener2.ToArray()); + Assert.AreEqual("DEF\r\n", textReceivedOnListener2); + } + + /// + /// Issue 1591 + /// + [TestMethod] + public void Ssh_ExecuteShellScript() + { + const string remoteFile = "/home/sshnet/run.sh"; + const string content = "#\bin\bash\necho Hello World!"; + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + if (client.Exists(remoteFile)) + { + client.DeleteFile(remoteFile); + } + + using (var memoryStream = new MemoryStream()) + using (var sw = new StreamWriter(memoryStream, Encoding.ASCII)) + { + sw.Write(content); + sw.Flush(); + memoryStream.Position = 0; + client.UploadFile(memoryStream, remoteFile); + } + } + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + try + { + var runChmod = client.RunCommand("chmod u+x " + remoteFile); + runChmod.Execute(); + Assert.AreEqual(0, runChmod.ExitStatus, runChmod.Error); + + var runLs = client.RunCommand("ls " + remoteFile); + var asyncResultLs = runLs.BeginExecute(); + + var runScript = client.RunCommand(remoteFile); + var asyncResultScript = runScript.BeginExecute(); + + Assert.IsTrue(asyncResultScript.AsyncWaitHandle.WaitOne(10000)); + var resultScript = runScript.EndExecute(asyncResultScript); + Assert.AreEqual("Hello World!\n", resultScript); + + Assert.IsTrue(asyncResultLs.AsyncWaitHandle.WaitOne(10000)); + var resultLs = runLs.EndExecute(asyncResultLs); + Assert.AreEqual(remoteFile + "\n", resultLs); + } + finally + { + RemoveFileOrDirectory(client, remoteFile); + } + } + } + + /// + /// Verifies if a hosts file contains an entry for a given combination of IP address and hostname, + /// and if necessary add either the host entry or an alias to an exist entry for the specified IP + /// address. + /// + /// + /// + /// + /// + /// if an entry was added or updated in the specified hosts file; otherwise, + /// . + /// + private static bool AddOrUpdateHostsEntry(IConnectionInfoFactory linuxAdminConnectionFactory, + IPAddress ipAddress, + string hostName) + { + const string hostsFile = "/etc/hosts"; + + using (var client = new ScpClient(linuxAdminConnectionFactory.Create())) + { + client.Connect(); + + var hostConfig = HostConfig.Read(client, hostsFile); + + var hostEntry = hostConfig.Entries.SingleOrDefault(h => h.IPAddress.Equals(ipAddress)); + if (hostEntry != null) + { + if (hostEntry.HostName == hostName) + { + return false; + } + + foreach (var alias in hostEntry.Aliases) + { + if (alias == hostName) + { + return false; + } + } + + hostEntry.Aliases.Add(hostName); + } + else + { + bool mappingFound = false; + + for (var i = (hostConfig.Entries.Count - 1); i >= 0; i--) + { + hostEntry = hostConfig.Entries[i]; + + if (hostEntry.HostName == hostName) + { + if (hostEntry.IPAddress.Equals(ipAddress)) + { + mappingFound = true; + continue; + } + + // If hostname is currently mapped to another IP address, then remove the + // current mapping + hostConfig.Entries.RemoveAt(i); + } + else + { + for (var j = (hostEntry.Aliases.Count - 1); j >= 0; j--) + { + var alias = hostEntry.Aliases[j]; + + if (alias == hostName) + { + hostEntry.Aliases.RemoveAt(j); + } + } + } + } + + if (!mappingFound) + { + hostEntry = new HostEntry(ipAddress, hostName); + hostConfig.Entries.Add(hostEntry); + } + } + + hostConfig.Write(client, hostsFile); + return true; + } + } + + /// + /// Remove the mapping between a given IP address and host name from the remote hosts file either by + /// removing a host entry entirely (if no other aliases are defined for the IP address) or removing + /// the aliases that match the host name for the IP address. + /// + /// + /// + /// + /// + /// if the hosts file was updated; otherwise, . + /// + private static bool RemoveHostsEntry(IConnectionInfoFactory linuxAdminConnectionFactory, + IPAddress ipAddress, + string hostName) + { + const string hostsFile = "/etc/hosts"; + + using (var client = new ScpClient(linuxAdminConnectionFactory.Create())) + { + client.Connect(); + + var hostConfig = HostConfig.Read(client, hostsFile); + + var hostEntry = hostConfig.Entries.SingleOrDefault(h => h.IPAddress.Equals(ipAddress)); + if (hostEntry == null) + { + return false; + } + + if (hostEntry.HostName == hostName) + { + if (hostEntry.Aliases.Count == 0) + { + hostConfig.Entries.Remove(hostEntry); + } + else + { + // Use one of the aliases (that are different from the specified host name) as host name + // of the host entry. + + for (var i = hostEntry.Aliases.Count - 1; i >= 0; i--) + { + var alias = hostEntry.Aliases[i]; + if (alias == hostName) + { + hostEntry.Aliases.RemoveAt(i); + } + else if (hostEntry.HostName == hostName) + { + // If we haven't already used one of the aliases as host name of the host entry + // then do this now and remove the alias. + + hostEntry.HostName = alias; + hostEntry.Aliases.RemoveAt(i); + } + } + + // If for some reason the host name of the host entry matched the specified host name + // and it only had aliases that match the host name, then remove the host entry altogether. + if (hostEntry.Aliases.Count == 0 && hostEntry.HostName == hostName) + { + hostConfig.Entries.Remove(hostEntry); + } + } + } + else + { + var aliasRemoved = false; + + for (var i = hostEntry.Aliases.Count - 1; i >= 0; i--) + { + if (hostEntry.Aliases[i] == hostName) + { + hostEntry.Aliases.RemoveAt(i); + aliasRemoved = true; + } + } + + if (!aliasRemoved) + { + return false; + } + } + + hostConfig.Write(client, hostsFile); + return true; + } + } + + private static string GetHttpResponse(Socket socket, Encoding encoding) + { + var httpResponseBuffer = new byte[2048]; + + // We expect: + // * The response to contain the searchText in the first receive. + // * The full response to be returned in the first receive. + + var bytesReceived = socket.Receive(httpResponseBuffer, + 0, + httpResponseBuffer.Length, + SocketFlags.None); + if (bytesReceived == 0) + { + return null; + } + + if (bytesReceived == httpResponseBuffer.Length) + { + throw new Exception("We expect the HTTP response to be less than the buffer size. If not, we won't consume the full response."); + } + + using (var sr = new StringReader(encoding.GetString(httpResponseBuffer, 0, bytesReceived))) + { + return sr.ReadToEnd(); + } + } + + private static void CreateShellScript(IConnectionInfoFactory connectionInfoFactory, string remoteFile, string script) + { + using (var sftpClient = new SftpClient(connectionInfoFactory.Create())) + { + sftpClient.Connect(); + + using (var sw = sftpClient.CreateText(remoteFile, new UTF8Encoding(false))) + { + sw.Write(script); + } + + sftpClient.ChangePermissions(remoteFile, 0x1FF); + } + } + + private static void RemoveFileOrDirectory(SshClient client, string remoteFile) + { + using (var cmd = client.CreateCommand("rm -Rf " + remoteFile)) + { + cmd.Execute(); + Assert.AreEqual(0, cmd.ExitStatus, cmd.Error); + } + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/TestBase.cs b/src/Renci.SshNet.IntegrationTests/TestBase.cs new file mode 100644 index 000000000..511bb144d --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/TestBase.cs @@ -0,0 +1,80 @@ +using System.Security.Cryptography; + +namespace Renci.SshNet.IntegrationTests +{ + public abstract class TestBase : IntegrationTestBase + { + protected static MemoryStream CreateMemoryStream(int size) + { + var memoryStream = new MemoryStream(); + FillStream(memoryStream, size); + return memoryStream; + } + + protected static void FillStream(Stream stream, int size) + { + var randomContent = new byte[50]; + var random = new Random(); + + var numberOfBytesToWrite = size; + + while (numberOfBytesToWrite > 0) + { + random.NextBytes(randomContent); + + var numberOfCharsToWrite = Math.Min(numberOfBytesToWrite, randomContent.Length); + stream.Write(randomContent, 0, numberOfCharsToWrite); + numberOfBytesToWrite -= numberOfCharsToWrite; + } + } + + protected static string CreateHash(Stream stream) + { + MD5 md5 = new MD5CryptoServiceProvider(); + var hash = md5.ComputeHash(stream); + return Encoding.ASCII.GetString(hash); + } + + protected static string CreateHash(byte[] buffer) + { + using (var ms = new MemoryStream(buffer)) + { + return CreateHash(ms); + } + } + + protected static string CreateFileHash(string path) + { + using (var fs = File.OpenRead(path)) + { + return CreateHash(fs); + } + } + + protected static string CreateTempFile(int size) + { + var file = Path.GetTempFileName(); + CreateFile(file, size); + return file; + } + + protected static void CreateFile(string fileName, int size) + { + using (var fs = File.OpenWrite(fileName)) + { + FillStream(fs, size); + } + } + + protected Stream GetManifestResourceStream(string resourceName) + { + var type = GetType(); + var resourceStream = type.Assembly.GetManifestResourceStream(resourceName); + if (resourceStream == null) + { + throw new ArgumentException($"Resource '{resourceName}' not found in assembly '{type.Assembly.FullName}'.", nameof(resourceName)); + } + return resourceStream; + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/TestInitializer.cs b/src/Renci.SshNet.IntegrationTests/TestInitializer.cs index 16b0a3eca..0c058781e 100644 --- a/src/Renci.SshNet.IntegrationTests/TestInitializer.cs +++ b/src/Renci.SshNet.IntegrationTests/TestInitializer.cs @@ -1,4 +1,4 @@ -namespace IntegrationTests +namespace Renci.SshNet.IntegrationTests { [TestClass] public class TestInitializer diff --git a/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs b/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs index 63e6c70ce..b98de1267 100644 --- a/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs +++ b/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs @@ -1,8 +1,8 @@ -using DotNet.Testcontainers.Images; -using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; +using DotNet.Testcontainers.Images; -namespace IntegrationTests.TestsFixtures +namespace Renci.SshNet.IntegrationTests.TestsFixtures { public sealed class InfrastructureFixture : IDisposable { @@ -20,11 +20,11 @@ public static InfrastructureFixture Instance } } - private IContainer? _sshServer; + private IContainer _sshServer; - private IFutureDockerImage? _sshServerImage; + private IFutureDockerImage _sshServerImage; - public string? SshServerHostName { get; set; } + public string SshServerHostName { get; set; } public ushort SshServerPort { get; set; } diff --git a/src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs b/src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs index 521f947cc..1d6658fc2 100644 --- a/src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs +++ b/src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs @@ -1,4 +1,4 @@ -namespace IntegrationTests.TestsFixtures +namespace Renci.SshNet.IntegrationTests.TestsFixtures { /// /// The base class for integration tests @@ -10,7 +10,7 @@ public abstract class IntegrationTestBase /// /// The SSH Server host name. /// - public string? SshServerHostName + public string SshServerHostName { get { @@ -62,5 +62,24 @@ private void ShowInfrastructureInformation() Console.WriteLine($"SSH Server host name: {_infrastructureFixture.SshServerHostName}"); Console.WriteLine($"SSH Server port: {_infrastructureFixture.SshServerPort}"); } + + /// + /// Creates the test file. + /// + /// Name of the file. + /// Size in megabytes. + protected void CreateTestFile(string fileName, int size) + { + using (var testFile = File.Create(fileName)) + { + var random = new Random(); + for (int i = 0; i < 1024 * size; i++) + { + var buffer = new byte[1024]; + random.NextBytes(buffer); + testFile.Write(buffer, 0, buffer.Length); + } + } + } } } diff --git a/src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs b/src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs index 5f2ee4d56..9a67f65c3 100644 --- a/src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs +++ b/src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs @@ -1,4 +1,4 @@ -namespace IntegrationTests.TestsFixtures +namespace Renci.SshNet.IntegrationTests.TestsFixtures { public class SshUser { diff --git a/src/Renci.SshNet.IntegrationTests/Users.cs b/src/Renci.SshNet.IntegrationTests/Users.cs new file mode 100644 index 000000000..043ab63ec --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/Users.cs @@ -0,0 +1,8 @@ +namespace Renci.SshNet.IntegrationTests +{ + internal static class Users + { + public static readonly Credential Regular = new Credential("sshnet", "ssh4ever"); + public static readonly Credential Admin = new Credential("sshnetadm", "ssh4ever"); + } +} diff --git a/src/Renci.SshNet.IntegrationTests/Usings.cs b/src/Renci.SshNet.IntegrationTests/Usings.cs index e6180a739..8eba0e510 100644 --- a/src/Renci.SshNet.IntegrationTests/Usings.cs +++ b/src/Renci.SshNet.IntegrationTests/Usings.cs @@ -1,9 +1,5 @@ -#pragma warning disable IDE0005 - global using System.Text; global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using IntegrationTests.TestsFixtures; - - +global using Renci.SshNet.IntegrationTests.TestsFixtures; diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/id_dsa b/src/Renci.SshNet.IntegrationTests/resources/client/id_dsa new file mode 100644 index 000000000..6c84e0c65 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/id_dsa @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQC1Zd32ntjuKsLACveUlEoV9CjFDT/spf7k95Rh/U/2abZx0pa0 +8Z01lwpdIPdShHgmhJww4S8ZuGgr9QIOAf8r3DOLt+KpJmjS8ti4gMYqnaG2XDRu +tT6sKneSA5Kd/CwbJ/LZm9dsbvTWpaVHzokhAdXMgq+MxWTK5tMLXciUFQIVAK76 +I2Sp/9g4BiNisdIIcWZYB8RhAoGADlSeN+FAEdx5+pQOZ1jXxTrlFR91u5yWj9BU +CYiD8exlG3cTvarQzU21pFi93PasefgezpXuMTO3L8lz6zUFGAxwhZUvlHtsdyHi +a5HX2ZB/Xjz9ucuQNCeP3PvF170Go+MwOZ38Nd6MuT7cne3dyqubRAzPColXSIcJ +F41ANz0CgYEAm8IGZQatS7M6AfNITNWG4TI7Z2aRQjLb9/MWJIID7c/VQ4zdTZdG +3kpk0Gj9n4xreopK5NmYAdj8rtFfPBgmXltsLqt+bBcXkpxW//7WC29WOXW3t90y +STh+cWuWfr9fV7mf4Ql/6u/ZIgpQNvnNYezazt3fK8EXjI1dAXEuQxECFBhGOzk+ +Aimeob964E8+HsQNlyde +-----END DSA PRIVATE KEY----- diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/id_dsa.ppk b/src/Renci.SshNet.IntegrationTests/resources/client/id_dsa.ppk new file mode 100644 index 000000000..b73384f82 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/id_dsa.ppk @@ -0,0 +1,17 @@ +PuTTY-User-Key-File-2: ssh-dss +Encryption: none +Comment: imported-openssh-key +Public-Lines: 10 +AAAAB3NzaC1kc3MAAACBALVl3fae2O4qwsAK95SUShX0KMUNP+yl/uT3lGH9T/Zp +tnHSlrTxnTWXCl0g91KEeCaEnDDhLxm4aCv1Ag4B/yvcM4u34qkmaNLy2LiAxiqd +obZcNG61Pqwqd5IDkp38LBsn8tmb12xu9NalpUfOiSEB1cyCr4zFZMrm0wtdyJQV +AAAAFQCu+iNkqf/YOAYjYrHSCHFmWAfEYQAAAIAOVJ434UAR3Hn6lA5nWNfFOuUV +H3W7nJaP0FQJiIPx7GUbdxO9qtDNTbWkWL3c9qx5+B7Ole4xM7cvyXPrNQUYDHCF +lS+Ue2x3IeJrkdfZkH9ePP25y5A0J4/c+8XXvQaj4zA5nfw13oy5Ptyd7d3Kq5tE +DM8KiVdIhwkXjUA3PQAAAIEAm8IGZQatS7M6AfNITNWG4TI7Z2aRQjLb9/MWJIID +7c/VQ4zdTZdG3kpk0Gj9n4xreopK5NmYAdj8rtFfPBgmXltsLqt+bBcXkpxW//7W +C29WOXW3t90ySTh+cWuWfr9fV7mf4Ql/6u/ZIgpQNvnNYezazt3fK8EXjI1dAXEu +QxE= +Private-Lines: 1 +AAAAFBhGOzk+Aimeob964E8+HsQNlyde +Private-MAC: 1c254f3882a6661c98fb82dea1a55638a23633e5 diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/id_noaccess.rsa b/src/Renci.SshNet.IntegrationTests/resources/client/id_noaccess.rsa new file mode 100644 index 000000000..cf2cc9795 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/id_noaccess.rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEoQIBAAKCAQEAuTtXn+BatX1oJuvhqfJZw5jc/pcIxJUPmuoFCH3+bXfKBJ/9 +4ixNETzZBasyvT/ozboAbCG3qcJOYxf2BEeTAIXe1jLAoTd1GKCwMvZOyjnsPN95 +/lChwfdnBbMzpZYTGfoUylXme/mzjjLu/J0qXgR5lyk9HFT+x5YEtRl8VSHiDkLK +TZ37dwhsqgcs+PkfvYMUK+C8evnfE0tgWgKZk0Eatl87nLWyVXB4LzhSDtGKLCPA +OgrX7fYfplDwJ2WK1N6nG0FnxW1HhDeSK7e2TbAa2vZQgvFXMWnO4O/NZKp4COpO +ReyliWhdtKAjr/+cD4yDfPjhjjKOYfxbvdRG4QIBIwKCAQAqVrTxV9o4HKoXhl93 +TVZYl/f/rX5Y0Z0quSW4zFdpendRg6e+qwpNFTjrWlS9ivNiOSSrAGR+ktAWpmQe +PD7bjFAw9ahfXSIUQfxja3+5Mc+Y4p+KlhZYOIyTlqy4Ik2CR8o84G8yR7QDPteK +Mo1XUXrguPgGedPV2SWlvK60XyAXqsewDhi7SeImZomKzbh33SXjVxakzHfa8BEU +eIIeR9oFlQMuYdo4GrHhFO2T+g/gqw/kVd1zkeEwt06fZVDErVwp+twewxxvwrk4 +CKUCzavfhDfi5sJ5YdzhDBRgkyBgJI+f15dKyqqOiAparV9+uzrD6vIuNnlVoqQA +iugLAoGBAPBliy32e83nshBknBn5HOK2rO3a1zHxvYr/NzITXtdZOjatNyfXtkwi +Ll/el5tZhJvKe9nItSI/4w7mvlvXZfW8h3MR0qb8at4jWa8ya2hwEerqaJonqjjb ++eBhg27ltZIQRk8Bv6ApXTAWkc+dFGhEIysokDQX7V72Bdrizup1AoGBAMVBLHK0 +5IFb8x7danlAmDX6bqCObId4Pce2OeONFIj1jIowvCXaE0t9zU4X5SdN5ujqu4Dq +XgzUdNeKcJxWpFO74MDRxT3CbMz36fikJnvxWl/+q0HalYuCY8gm14VYcThUBAro +3c941INueybGNLIA9jc7RMnsFtyVTvNYpaU9AoGAFJr9TRUgjf3qsPKuS15+0Zqh +G7OsC5hgtCSBEuu3rA72XHU/Pe3rDdcLSgvD2h2dpvQZPo2L3l0/WQx2t2o78H3f +uWftfAcB2Iav6nIJNNZn75BvXaug4E1ej5NUaJdYtL+Q/3UtrqR1s6opwVabWWTt +ElPvGmhzboodwk30en8CgYAyuPzNCfGdm00lMZ8JPH7pTwaBDq4xdrDM9FgHUCna +E0FlXP0uTgT2J6nSQKijtPI75JadfhgvL1E+vTLmX2wViBU45XvcrlZ92Vlr0nBL +wbgnUB1otIzauyD49AuIsFegxSWcZ8QCJmKIMlouir0X1FyR3Apfzv6Qfio+kyNH +vwKBgQCtwxojkzUSfV3zDt6bYSLBzgXgo/Zr9lS+gSggP72DzINmW2gbA0fkM2Zu +JltcfakKv4gVX/1zooz+7t+4bj6dqt+bl7hYz0VnTSDZGuo5LKDif/4gSGrdblC2 +QLTuX2HjWCZdsue7mRwL7cXR4zlIoE99+Ryhdxvc5wHSfYr/JA== +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/id_rsa b/src/Renci.SshNet.IntegrationTests/resources/client/id_rsa new file mode 100644 index 000000000..da8f397ea --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuDdE+laKZ8D0mEFMNMGkDYQY89A2BUML5L+DsFmEn//3yRdz +Omx+Nk59ifiJUPuP3whZxN5vfbHleU9ZshI+37LqTbXmNrChRlxDSxrQA/++hTNI +gZl8e+sZUk8mUkpjQyEVOMVEdt3H3PmpfOBeZjtpRJPuWa90J+SVKbnAX4rOL7bt +LDA4GeMNXdLXLl5E/zBCZ9ol9nJ8W0suajQDx7u2/ixH1wb9jqzA6j68f1+kERII +t8OUcS+ZM+274rVrCMCL290k9gEQAKwcG/KRMqdgP8Oadtq5ELS/t4m2fH5JqYJ9 +9399QGT4LTPvoiTwZjyhYwK3FhCfXyFQf+gwIwIDAQABAoIBAGLChsFrKfJr2PXT +dAaIleoFGteDlaKGilbNcc1WgKrCsNXnM4hr59I3jEgurXd0FnKs6GuKEN2jRPIf +X2f/LiQBqGmXDl/dm+i7x/v42PJ75mlE0Cdi4QESTlX5RwMxDDxN/TGdWJIdXmwS +kRH4u8M1ML9qS4tba/uDKZDgG8lcF/z6K0RbXDMxL0azfbd5e+jca1e8Fs93X5s7 +mJotXaA+L33R7lCpBBOa1OY517Ug7bdI+uWh59o0bw8v2q8vr8ISOHU3N+JvKZ62 +2z5O6lLyB94sF6ltXRv9pmfcSBjfB8CJx5q1yejUZKM6VfN98MTSKo8WKMEtGmqk +BtZFTlECgYEA8WYG6vntqXPCHhFFfgCymh8OoXL/mPDSkqsswKfeD4O+Ml9hRUtg +xl6FDxFAMR7WJ4Vb8u9IMOc7Xx+nzlZNsdC5m7FAVRIEUPPjEucte+ZYKjSy+WOd +dAtQ07O3z9fi+JNplSjisKtBWaemfqc2TcYXOeIIgwJnkaCf2C7I2bsCgYEAw1vJ +9c5VLTisPj7ijMeGLWISG5E0aidOrb15E8xcnXuT9TEW1Dc1EgRK/D03tlnoxr1I +CISPx4EmdLTiEl2AVi33DOhCeFAt8TOd/y3chKsbewb+BYEMmBD93mhsKg+YmC5E +284SCV3fCcyFJfo9oy5Z2tIELerjT0cnpHgPCLkCgYEAij3OemRUeTUklol3jXgi +z+Y3P7gWreREAuBqSY4YujPNCRXcI43ORuu8MWvEohyxsYJKrO3hHrhdJNWBCMYd +ylXo5UN1vwIJXL6+bIXdY1X/aXQyhmVItzr/t6z0997/STFKRrRaVahNTWWYEHH7 +xEBL7scF7tjCrQAaafgo558CgYBkrrm3ZU+grsSWj/JSe8I7QX/zlTJeQ0PZZv0v +pvNUdowaoeISHSHM10mOFj7QTCYbxxGI0kkHmRgordCVhnrN74KTtGANgcUrul6D +VS+BcG4JSeFBFPFYreko5shYJRGP3MjAP8Qr76Uzd6RnnkCGCS1mCTb+M0BTa2iS +6w1UgQKBgQDQ2qV4s7xH3dixy4MWhDBFmrQlFpQkNNkrJ/ImHrxI2tFyNaq1fE+Q +PrXJi8mjwb/ETVN2C5iBTtIyVg1pZk3YAWAvIt9SPaRVYQWj8IJeOTTaNEZEvp5K +1LJBWO0ksgJK28f/z7FwejeGbBwg8ch9wVhtFwIV++rZ76smP7C+9Q== +-----END RSA PRIVATE KEY----- diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/id_rsa.pub b/src/Renci.SshNet.IntegrationTests/resources/client/id_rsa.pub new file mode 100644 index 000000000..24ba2f7dd --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4N0T6VopnwPSYQUw0waQNhBjz0DYFQwvkv4OwWYSf//fJF3M6bH42Tn2J+IlQ+4/fCFnE3m99seV5T1myEj7fsupNteY2sKFGXENLGtAD/76FM0iBmXx76xlSTyZSSmNDIRU4xUR23cfc+al84F5mO2lEk+5Zr3Qn5JUpucBfis4vtu0sMDgZ4w1d0tcuXkT/MEJn2iX2cnxbSy5qNAPHu7b+LEfXBv2OrMDqPrx/X6QREgi3w5RxL5kz7bvitWsIwIvb3ST2ARAArBwb8pEyp2A/w5p22rkQtL+3ibZ8fkmpgn33f31AZPgtM++iJPBmPKFjArcWEJ9fIVB/6DAj diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/id_rsa_with_pass b/src/Renci.SshNet.IntegrationTests/resources/client/id_rsa_with_pass new file mode 100644 index 000000000..841532d1f --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/id_rsa_with_pass @@ -0,0 +1,28 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABC9v0UCCP +T+1yNEu9m0w939AAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQC4N0T6Vopn +wPSYQUw0waQNhBjz0DYFQwvkv4OwWYSf//fJF3M6bH42Tn2J+IlQ+4/fCFnE3m99seV5T1 +myEj7fsupNteY2sKFGXENLGtAD/76FM0iBmXx76xlSTyZSSmNDIRU4xUR23cfc+al84F5m +O2lEk+5Zr3Qn5JUpucBfis4vtu0sMDgZ4w1d0tcuXkT/MEJn2iX2cnxbSy5qNAPHu7b+LE +fXBv2OrMDqPrx/X6QREgi3w5RxL5kz7bvitWsIwIvb3ST2ARAArBwb8pEyp2A/w5p22rkQ +tL+3ibZ8fkmpgn33f31AZPgtM++iJPBmPKFjArcWEJ9fIVB/6DAjAAADwPBAapXaoQvm3O +2i9sBnmO+d8kCdm2nhGbEXNzswb0toARYyx7/rPON15BHv470NLK4GjtxWb8SbkUBjWIXJ +dpJR1feYAgJQ27yaU6SBEJvnQBFI3EvH+h9ykaikDP/SzgZuGup5NZIoB09PzCPk4SbAwn +skFT3s1v3ufaULYTiAO2xtWzABkjxfw8HOo+PF3UqGIF4145kGLUT1pUN7iy5EQZA8evRb +Yt/9+ChcyBgKONgXdpjLNf02XIM/jQofZkROBg7ZAKCjtL3yGpvtOpwzCKy1hWDmgjtkeK +Xn84/qwXWEobBa2wrDQ4Mjj7AIimRsCciO05bVB5KtNjT+WzCalpTzfj2nazukteNRKTD3 +bQR0gLFfFX4/YodXmtu2n+0R2AKkdPW5ZhoEEpT1FjfYUImAuElSEy5FEeR3bwE5uGQkF4 +uIMJWD+89QxO9PWKTloVI2hrOF9/z+UzUi7p16FQFDlB82qCQAiaIOHgotgDn9+OwMw0ew +Gu/D3T8ZpKXcTAxK1JeoDFh2h+CE37JDvftNIxIhTp7lrhCdioj1DSBorwfke/q4+OvLUH +8SZ6ZgppHjJ4jg6lB9TWCpD5PECDW+NuQQwUb5V4NKoBqnJrPaNUSbI7SL5Pq3mOXXNRmv +q1Va06CfcHL3JupICFifux5xBDlQY0foAt7DFOgA6qANaCnRl2H2oFsEdRhBbL6EPP0bAQ +7PBvWJlt5Aqr1V7QzcCHNZcyGpjiHeXsQxSWXzb1xQprlrDSnlGZpusrBTcZTLWUtOAgqQ +dNbUNeUq1E74rZ/RBiVliaLHNBKwTCE9KyZTl/DcfgInB7DDEd7Vlmujzar7xAXRJot7k2 +a12gT69eVsqiO5si23493JFl0JB5vbQvQWARAvcdNPDPiILoJVpbgyL9oMzVzTjJyqYS1x +cHXKE+rL4ZHeQhcfySXplZlUY9ADVY5QywAj2kl+5gT3Fohu/95axF7w9dAHyMntxbVnA3 +r0zOmqbAKOYEYEXxZ+Vq09YdEyJh33V48PUmvCusWWyeKIfjO4R1nD2+7iyiF7joF7bOBm +o0LXuJdr81BCMueryPUaqSRpGhg/P/nUqzxj7p0mAh9uUOr/CtOpbc6wNY70RgdiMGFWhz +zhO54p7iEg2zZNUZ5zRM1hTBikTeYyInpw8IQiMv9GH7/9gWwqPsh0qRVjj5kjqjSu069d +FeuGjsMGCnLLG6WvOQ9DgH9JR4cfinBL3JwtpHFBIHwhsh2EIuSseVHvQlFlEc1U+7Yoxc +0dn1VVtg== +-----END OPENSSH PRIVATE KEY----- diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_256_openssh b/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_256_openssh new file mode 100644 index 000000000..29ecb9073 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_256_openssh @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQT886z6SLRIzRu7VNA6SSeKZCNNRPXe +iutTik1T3RUEshgnTI/V3T/d5QurCQPvf2ob3+Rd4FhCsVCS9gilIhVsAAAAsH3KX4d9yl ++HAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPzzrPpItEjNG7tU +0DpJJ4pkI01E9d6K61OKTVPdFQSyGCdMj9XdP93lC6sJA+9/ahvf5F3gWEKxUJL2CKUiFW +wAAAAgYxeSyo7MVNup52COOCarcvARKlWhKIP2CKzj4qa5/6EAAAAYc3NobmV0QFVidW50 +dTE5MTBEZXNrdG9w +-----END OPENSSH PRIVATE KEY----- diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_256_openssh.pub b/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_256_openssh.pub new file mode 100644 index 000000000..33524cea6 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_256_openssh.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPzzrPpItEjNG7tU0DpJJ4pkI01E9d6K61OKTVPdFQSyGCdMj9XdP93lC6sJA+9/ahvf5F3gWEKxUJL2CKUiFWw= sshnet@Ubuntu1910Desktop diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_384_openssh b/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_384_openssh new file mode 100644 index 000000000..e720d2407 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_384_openssh @@ -0,0 +1,10 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS +1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQS0rLvxzSomgC4UNulA7/jdABXTG9un +zFazinvOughrumo0n/R1DoSRXY4bHooQdq02pTD6/DK3FzS7n4ouJi/LuJfX1EFxYMJzP2 +aYlT0rOgvvIuLv2Q4OzcdjV8mzSIEAAADo1T5ZUtU+WVIAAAATZWNkc2Etc2hhMi1uaXN0 +cDM4NAAAAAhuaXN0cDM4NAAAAGEEtKy78c0qJoAuFDbpQO/43QAV0xvbp8xWs4p7zroIa7 +pqNJ/0dQ6EkV2OGx6KEHatNqUw+vwytxc0u5+KLiYvy7iX19RBcWDCcz9mmJU9KzoL7yLi +79kODs3HY1fJs0iBAAAAMQDgRb336Dk9e4VxOpSqnwBqHRsJ3QmSME9qMBvx5SXykHFAsK +wzVKEvIQizmg/+sWcAAAAYc3NobmV0QFVidW50dTE5MTBEZXNrdG9wAQIDBAUGBw== +-----END OPENSSH PRIVATE KEY----- diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_384_openssh.pub b/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_384_openssh.pub new file mode 100644 index 000000000..99878d2ca --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_384_openssh.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLSsu/HNKiaALhQ26UDv+N0AFdMb26fMVrOKe866CGu6ajSf9HUOhJFdjhseihB2rTalMPr8MrcXNLufii4mL8u4l9fUQXFgwnM/ZpiVPSs6C+8i4u/ZDg7Nx2NXybNIgQ== sshnet@Ubuntu1910Desktop diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_521_openssh b/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_521_openssh new file mode 100644 index 000000000..47ee8ff8b --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_521_openssh @@ -0,0 +1,12 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS +1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQAgeFoEYBgUaOlJPnEYIPLSTwmxLRl +EUVpVAOzww3q10fj/Tuppty/fRLcbMoeVzWKl8mjDbR+XOdaKDGo6xcHGsgByITz2/F9wr +E8BHyFEPemg8h0DKLW0X55J+rnn3lE0a0jXKngJ3VLVcgKgXam7KtpoCWFx689jVCpTxWI +GrkIvlkAAAEYr0LrEa9C6xEAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ +AAAIUEAIHhaBGAYFGjpST5xGCDy0k8JsS0ZRFFaVQDs8MN6tdH4/07qabcv30S3GzKHlc1 +ipfJow20flznWigxqOsXBxrIAciE89vxfcKxPAR8hRD3poPIdAyi1tF+eSfq5595RNGtI1 +yp4Cd1S1XICoF2puyraaAlhcevPY1QqU8ViBq5CL5ZAAAAQQ50pmBidmKTIknaRpdO5WIu +nYEGMUkLqdZ0egk9Ggg63mOHiLykf+XWcGbHbHM95CISXhqlvMtCYeGwOpP6FoMGAAAAGH +NzaG5ldEBVYnVudHUxOTEwRGVza3RvcAECAw== +-----END OPENSSH PRIVATE KEY----- diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_521_openssh.pub b/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_521_openssh.pub new file mode 100644 index 000000000..085fa07bf --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/key_ecdsa_521_openssh.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACB4WgRgGBRo6Uk+cRgg8tJPCbEtGURRWlUA7PDDerXR+P9O6mm3L99Etxsyh5XNYqXyaMNtH5c51ooMajrFwcayAHIhPPb8X3CsTwEfIUQ96aDyHQMotbRfnkn6uefeUTRrSNcqeAndUtVyAqBdqbsq2mgJYXHrz2NUKlPFYgauQi+WQ== sshnet@Ubuntu1910Desktop diff --git a/src/Renci.SshNet.IntegrationTests/resources/client/key_ed25519_openssh b/src/Renci.SshNet.IntegrationTests/resources/client/key_ed25519_openssh new file mode 100644 index 000000000..0bcb6e755 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/resources/client/key_ed25519_openssh @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAJDRj1Tk7s7ik4Bnx3L3tjEY+e8l4ZmYJFMovUaZia5QAAAKBKTcfHSk3H +xwAAAAtzc2gtZWQyNTUxOQAAACAJDRj1Tk7s7ik4Bnx3L3tjEY+e8l4ZmYJFMovUaZia5Q +AAAEBMMOaGa8RU8Vy0vLFlRT5iVSxl3ji9NBKaO/RS0aFL3QkNGPVOTuzuKTgGfHcve2MR +j57yXhmZgkUyi9RpmJrlAAAAGHNzaG5ldEBVYnVudHUxOTEwRGVza3RvcAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/src/Renci.SshNet.IntegrationTests/resources/issue #70.png b/src/Renci.SshNet.IntegrationTests/resources/issue #70.png new file mode 100644 index 000000000..8c9723796 Binary files /dev/null and b/src/Renci.SshNet.IntegrationTests/resources/issue #70.png differ diff --git a/src/Renci.SshNet.TestTools.OpenSSH/Cipher.cs b/src/Renci.SshNet.TestTools.OpenSSH/Cipher.cs new file mode 100644 index 000000000..f7d8d8842 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/Cipher.cs @@ -0,0 +1,54 @@ +namespace Renci.SshNet.TestTools.OpenSSH +{ + public class Cipher + { + public static readonly Cipher TripledesCbc = new Cipher("3des-cbc"); + public static readonly Cipher Aes128Cbc = new Cipher("aes128-cbc"); + public static readonly Cipher Aes192Cbc = new Cipher("aes192-cbc"); + public static readonly Cipher Aes256Cbc = new Cipher("aes256-cbc"); + public static readonly Cipher RijndaelCbc = new Cipher("rijndael-cbc@lysator.liu.se"); + public static readonly Cipher Aes128Ctr = new Cipher("aes128-ctr"); + public static readonly Cipher Aes192Ctr = new Cipher("aes192-ctr"); + public static readonly Cipher Aes256Ctr = new Cipher("aes256-ctr"); + public static readonly Cipher Aes128Gcm = new Cipher("aes128-gcm@openssh.com"); + public static readonly Cipher Aes256Gcm = new Cipher("aes256-gcm@openssh.com"); + public static readonly Cipher Arcfour = new Cipher("arcfour"); + public static readonly Cipher Arcfour128 = new Cipher("arcfour128"); + public static readonly Cipher Arcfour256 = new Cipher("arcfour256"); + public static readonly Cipher BlowfishCbc = new Cipher("blowfish-cbc"); + public static readonly Cipher Cast128Cbc = new Cipher("cast128-cbc"); + public static readonly Cipher Chacha20Poly1305 = new Cipher("chacha20-poly1305@openssh.com"); + + public Cipher(string name) + { + Name = name; + } + + public string Name { get; } + + public override bool Equals(object? obj) + { + if (obj == null) + { + return false; + } + + if (obj is Cipher otherCipher) + { + return otherCipher.Name == Name; + } + + return false; + } + + public override int GetHashCode() + { + return Name.GetHashCode(); + } + + public override string ToString() + { + return Name; + } + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/Formatters/BooleanFormatter.cs b/src/Renci.SshNet.TestTools.OpenSSH/Formatters/BooleanFormatter.cs new file mode 100644 index 000000000..3eab2b199 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/Formatters/BooleanFormatter.cs @@ -0,0 +1,20 @@ +namespace Renci.SshNet.TestTools.OpenSSH.Formatters +{ + internal class BooleanFormatter + { + public string Format(bool value) + { + return value ? "yes" : "no"; + } + + public string Format(bool? value, bool defaultValue) + { + if (value.HasValue) + { + return Format(value.Value); + } + + return Format(defaultValue); + } + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/Formatters/Int32Formatter.cs b/src/Renci.SshNet.TestTools.OpenSSH/Formatters/Int32Formatter.cs new file mode 100644 index 000000000..2b8231111 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/Formatters/Int32Formatter.cs @@ -0,0 +1,12 @@ +using System.Globalization; + +namespace Renci.SshNet.TestTools.OpenSSH.Formatters +{ + internal class Int32Formatter + { + public string Format(int value) + { + return value.ToString(NumberFormatInfo.InvariantInfo); + } + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/Formatters/LogLevelFormatter.cs b/src/Renci.SshNet.TestTools.OpenSSH/Formatters/LogLevelFormatter.cs new file mode 100644 index 000000000..f9f4bbf6f --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/Formatters/LogLevelFormatter.cs @@ -0,0 +1,10 @@ +namespace Renci.SshNet.TestTools.OpenSSH.Formatters +{ + internal class LogLevelFormatter + { + public string Format(LogLevel logLevel) + { + return logLevel.ToString("G").ToUpperInvariant(); + } + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/Formatters/MatchFormatter.cs b/src/Renci.SshNet.TestTools.OpenSSH/Formatters/MatchFormatter.cs new file mode 100644 index 000000000..df2968be0 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/Formatters/MatchFormatter.cs @@ -0,0 +1,54 @@ +namespace Renci.SshNet.TestTools.OpenSSH.Formatters +{ + internal class MatchFormatter + { + public string Format(Match match) + { + using (var writer = new StringWriter()) + { + Format(match, writer); + return writer.ToString(); + } + } + + public void Format(Match match, TextWriter writer) + { + writer.Write("Match "); + + if (match.Users.Length > 0) + { + writer.Write("User "); + for (var i = 0; i < match.Users.Length; i++) + { + if (i > 0) + { + writer.Write(','); + } + + writer.Write(match.Users[i]); + } + } + + if (match.Addresses.Length > 0) + { + writer.Write("Address "); + for (var i = 0; i < match.Addresses.Length; i++) + { + if (i > 0) + { + writer.Write(','); + } + + writer.Write(match.Addresses[i]); + } + } + + writer.WriteLine(); + + if (match.AuthenticationMethods != null) + { + writer.WriteLine(" AuthenticationMethods " + match.AuthenticationMethods); + } + } + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/Formatters/SubsystemFormatter.cs b/src/Renci.SshNet.TestTools.OpenSSH/Formatters/SubsystemFormatter.cs new file mode 100644 index 000000000..fcb74e266 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/Formatters/SubsystemFormatter.cs @@ -0,0 +1,10 @@ +namespace Renci.SshNet.TestTools.OpenSSH.Formatters +{ + internal class SubsystemFormatter + { + public string Format(Subsystem subsystem) + { + return subsystem.Name + " " + subsystem.Command; + } + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/HostKeyAlgorithm.cs b/src/Renci.SshNet.TestTools.OpenSSH/HostKeyAlgorithm.cs new file mode 100644 index 000000000..0c79f7792 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/HostKeyAlgorithm.cs @@ -0,0 +1,53 @@ +namespace Renci.SshNet.TestTools.OpenSSH +{ + public class HostKeyAlgorithm + { + public static readonly HostKeyAlgorithm EcdsaSha2Nistp256CertV01OpenSSH = new HostKeyAlgorithm("ecdsa-sha2-nistp256-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm EcdsaSha2Nistp384CertV01OpenSSH = new HostKeyAlgorithm("ecdsa-sha2-nistp384-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm EcdsaSha2Nistp521CertV01OpenSSH = new HostKeyAlgorithm("ecdsa-sha2-nistp521-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm SshEd25519CertV01OpenSSH = new HostKeyAlgorithm("ssh-ed25519-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm RsaSha2256CertV01OpenSSH = new HostKeyAlgorithm("rsa-sha2-256-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm RsaSha2512CertV01OpenSSH = new HostKeyAlgorithm("rsa-sha2-512-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm SshRsaCertV01OpenSSH = new HostKeyAlgorithm("ssh-rsa-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm EcdsaSha2Nistp256 = new HostKeyAlgorithm("ecdsa-sha2-nistp256"); + public static readonly HostKeyAlgorithm EcdsaSha2Nistp384 = new HostKeyAlgorithm("ecdsa-sha2-nistp384"); + public static readonly HostKeyAlgorithm EcdsaSha2Nistp521 = new HostKeyAlgorithm("ecdsa-sha2-nistp521"); + public static readonly HostKeyAlgorithm SshEd25519 = new HostKeyAlgorithm("ssh-ed25519"); + public static readonly HostKeyAlgorithm RsaSha2512 = new HostKeyAlgorithm("rsa-sha2-512"); + public static readonly HostKeyAlgorithm RsaSha2256 = new HostKeyAlgorithm("rsa-sha2-256"); + public static readonly HostKeyAlgorithm SshRsa = new HostKeyAlgorithm("ssh-rsa"); + public static readonly HostKeyAlgorithm SshDsa = new HostKeyAlgorithm("ssh-dsa"); + + public HostKeyAlgorithm(string name) + { + Name = name; + } + + public string Name { get; } + + public override bool Equals(object? obj) + { + if (obj == null) + { + return false; + } + + if (obj is HostKeyAlgorithm otherHka) + { + return otherHka.Name == Name; + } + + return false; + } + + public override int GetHashCode() + { + return Name.GetHashCode(); + } + + public override string ToString() + { + return Name; + } + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/KeyExchangeAlgorithm.cs b/src/Renci.SshNet.TestTools.OpenSSH/KeyExchangeAlgorithm.cs new file mode 100644 index 000000000..4701103f0 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/KeyExchangeAlgorithm.cs @@ -0,0 +1,52 @@ +namespace Renci.SshNet.TestTools.OpenSSH +{ + public class KeyExchangeAlgorithm + { + public static readonly KeyExchangeAlgorithm DiffieHellmanGroup1Sha1 = new KeyExchangeAlgorithm("diffie-hellman-group1-sha1"); + public static readonly KeyExchangeAlgorithm DiffieHellmanGroup14Sha1 = new KeyExchangeAlgorithm("diffie-hellman-group14-sha1"); + public static readonly KeyExchangeAlgorithm DiffieHellmanGroup14Sha256 = new KeyExchangeAlgorithm("diffie-hellman-group14-sha256"); + public static readonly KeyExchangeAlgorithm DiffieHellmanGroup16Sha512 = new KeyExchangeAlgorithm("diffie-hellman-group16-sha512"); + public static readonly KeyExchangeAlgorithm DiffieHellmanGroup18Sha512 = new KeyExchangeAlgorithm("diffie-hellman-group18-sha512"); + public static readonly KeyExchangeAlgorithm DiffieHellmanGroupExchangeSha1 = new KeyExchangeAlgorithm("diffie-hellman-group-exchange-sha1"); + public static readonly KeyExchangeAlgorithm DiffieHellmanGroupExchangeSha256 = new KeyExchangeAlgorithm("diffie-hellman-group-exchange-sha256"); + public static readonly KeyExchangeAlgorithm EcdhSha2Nistp256 = new KeyExchangeAlgorithm("ecdh-sha2-nistp256"); + public static readonly KeyExchangeAlgorithm EcdhSha2Nistp384 = new KeyExchangeAlgorithm("ecdh-sha2-nistp384"); + public static readonly KeyExchangeAlgorithm EcdhSha2Nistp521 = new KeyExchangeAlgorithm("ecdh-sha2-nistp521"); + public static readonly KeyExchangeAlgorithm Curve25519Sha256 = new KeyExchangeAlgorithm("curve25519-sha256"); + public static readonly KeyExchangeAlgorithm Curve25519Sha256Libssh = new KeyExchangeAlgorithm("curve25519-sha256@libssh.org"); + public static readonly KeyExchangeAlgorithm Sntrup4591761x25519Sha512 = new KeyExchangeAlgorithm("sntrup4591761x25519-sha512@tinyssh.org"); + + + public KeyExchangeAlgorithm(string name) + { + Name = name; + } + + public string Name { get; } + + public override bool Equals(object? obj) + { + if (obj == null) + { + return false; + } + + if (obj is KeyExchangeAlgorithm otherKex) + { + return otherKex.Name == Name; + } + + return false; + } + + public override int GetHashCode() + { + return Name.GetHashCode(); + } + + public override string ToString() + { + return Name; + } + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/LogLevel.cs b/src/Renci.SshNet.TestTools.OpenSSH/LogLevel.cs new file mode 100644 index 000000000..8724ae068 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/LogLevel.cs @@ -0,0 +1,15 @@ +namespace Renci.SshNet.TestTools.OpenSSH +{ + public enum LogLevel + { + Quiet = 1, + Fatal = 2, + Error = 3, + Info = 4, + Verbose = 5, + Debug = 6, + Debug1 = 7, + Debug2 = 8, + Debug3 = 9 + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/Match.cs b/src/Renci.SshNet.TestTools.OpenSSH/Match.cs new file mode 100644 index 000000000..16cd5073d --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/Match.cs @@ -0,0 +1,57 @@ +namespace Renci.SshNet.TestTools.OpenSSH +{ + public class Match + { + public Match(string[] users, string[] addresses) + { + Users = users; + Addresses = addresses; + } + + public string[] Users { get; } + + public string[] Addresses { get; } + + public string? AuthenticationMethods { get; set; } + + public void WriteTo(TextWriter writer) + { + writer.Write("Match "); + + if (Users.Length > 0) + { + writer.Write("User "); + for (var i = 0; i < Users.Length; i++) + { + if (i > 0) + { + writer.Write(','); + } + + writer.Write(Users[i]); + } + } + + if (Addresses.Length > 0) + { + writer.Write("Address "); + for (var i = 0; i < Addresses.Length; i++) + { + if (i > 0) + { + writer.Write(','); + } + + writer.Write(Addresses[i]); + } + } + + writer.WriteLine(); + + if (AuthenticationMethods != null) + { + writer.WriteLine(" AuthenticationMethods " + AuthenticationMethods); + } + } + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/MessageAuthenticationCodeAlgorithm.cs b/src/Renci.SshNet.TestTools.OpenSSH/MessageAuthenticationCodeAlgorithm.cs new file mode 100644 index 000000000..17bf0cf91 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/MessageAuthenticationCodeAlgorithm.cs @@ -0,0 +1,56 @@ +namespace Renci.SshNet.TestTools.OpenSSH +{ + public class MessageAuthenticationCodeAlgorithm + { + public static readonly MessageAuthenticationCodeAlgorithm HmacMd5 = new MessageAuthenticationCodeAlgorithm("hmac-md5"); + public static readonly MessageAuthenticationCodeAlgorithm HmacMd5_96 = new MessageAuthenticationCodeAlgorithm("hmac-md5-96"); + public static readonly MessageAuthenticationCodeAlgorithm HmacRipemd160 = new MessageAuthenticationCodeAlgorithm("hmac-ripemd160"); + public static readonly MessageAuthenticationCodeAlgorithm HmacSha1 = new MessageAuthenticationCodeAlgorithm("hmac-sha1"); + public static readonly MessageAuthenticationCodeAlgorithm HmacSha1_96 = new MessageAuthenticationCodeAlgorithm("hmac-sha1-96"); + public static readonly MessageAuthenticationCodeAlgorithm HmacSha2_256 = new MessageAuthenticationCodeAlgorithm("hmac-sha2-256"); + public static readonly MessageAuthenticationCodeAlgorithm HmacSha2_512 = new MessageAuthenticationCodeAlgorithm("hmac-sha2-512"); + public static readonly MessageAuthenticationCodeAlgorithm Umac64 = new MessageAuthenticationCodeAlgorithm("umac-64@openssh.com"); + public static readonly MessageAuthenticationCodeAlgorithm Umac128 = new MessageAuthenticationCodeAlgorithm("umac-128@openssh.com"); + public static readonly MessageAuthenticationCodeAlgorithm HmacMd5Etm = new MessageAuthenticationCodeAlgorithm("hmac-md5-etm@openssh.com"); + public static readonly MessageAuthenticationCodeAlgorithm HmacMd5_96_Etm = new MessageAuthenticationCodeAlgorithm("hmac-md5-96-etm@openssh.com"); + public static readonly MessageAuthenticationCodeAlgorithm HmacRipemd160Etm = new MessageAuthenticationCodeAlgorithm("hmac-ripemd160-etm@openssh.com"); + public static readonly MessageAuthenticationCodeAlgorithm HmacSha1Etm = new MessageAuthenticationCodeAlgorithm("hmac-sha1-etm@openssh.com"); + public static readonly MessageAuthenticationCodeAlgorithm HmacSha1_96_Etm = new MessageAuthenticationCodeAlgorithm("hmac-sha1-96-etm@openssh.com"); + public static readonly MessageAuthenticationCodeAlgorithm HmacSha2_256_Etm = new MessageAuthenticationCodeAlgorithm("hmac-sha2-256-etm@openssh.com"); + public static readonly MessageAuthenticationCodeAlgorithm HmacSha2_512_Etm = new MessageAuthenticationCodeAlgorithm("hmac-sha2-512-etm@openssh.com"); + public static readonly MessageAuthenticationCodeAlgorithm Umac64_Etm = new MessageAuthenticationCodeAlgorithm("umac-64-etm@openssh.com"); + public static readonly MessageAuthenticationCodeAlgorithm Umac128_Etm = new MessageAuthenticationCodeAlgorithm("umac-128-etm@openssh.com"); + + public MessageAuthenticationCodeAlgorithm(string name) + { + Name = name; + } + + public string Name { get; } + + public override bool Equals(object? obj) + { + if (obj == null) + { + return false; + } + + if (obj is MessageAuthenticationCodeAlgorithm otherMac) + { + return otherMac.Name == Name; + } + + return false; + } + + public override int GetHashCode() + { + return Name.GetHashCode(); + } + + public override string ToString() + { + return Name; + } + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/PublicKeyAlgorithm.cs b/src/Renci.SshNet.TestTools.OpenSSH/PublicKeyAlgorithm.cs new file mode 100644 index 000000000..292ace7f9 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/PublicKeyAlgorithm.cs @@ -0,0 +1,59 @@ +namespace Renci.SshNet.TestTools.OpenSSH +{ + public class PublicKeyAlgorithm + { + public static readonly PublicKeyAlgorithm SshEd25519 = new PublicKeyAlgorithm("ssh-ed25519"); + public static readonly PublicKeyAlgorithm SshEd25519CertV01OpenSSH = new PublicKeyAlgorithm("ssh-ed25519-cert-v01@openssh.com"); + public static readonly PublicKeyAlgorithm SkSshEd25519OpenSSH = new PublicKeyAlgorithm("sk-ssh-ed25519@openssh.com"); + public static readonly PublicKeyAlgorithm SkSshEd25519CertV01OpenSSH = new PublicKeyAlgorithm("sk-ssh-ed25519-cert-v01@openssh.com"); + public static readonly PublicKeyAlgorithm SshRsa = new PublicKeyAlgorithm("ssh-rsa"); + public static readonly PublicKeyAlgorithm RsaSha2256 = new PublicKeyAlgorithm("rsa-sha2-256"); + public static readonly PublicKeyAlgorithm RsaSha2512 = new PublicKeyAlgorithm("rsa-sha2-512"); + public static readonly PublicKeyAlgorithm SshDss = new PublicKeyAlgorithm("ssh-dss"); + public static readonly PublicKeyAlgorithm EcdsaSha2Nistp256 = new PublicKeyAlgorithm("ecdsa-sha2-nistp256"); + public static readonly PublicKeyAlgorithm EcdsaSha2Nistp384 = new PublicKeyAlgorithm("ecdsa-sha2-nistp384"); + public static readonly PublicKeyAlgorithm EcdsaSha2Nistp521 = new PublicKeyAlgorithm("ecdsa-sha2-nistp521"); + public static readonly PublicKeyAlgorithm SkEcdsaSha2Nistp256OpenSSH = new PublicKeyAlgorithm("sk-ecdsa-sha2-nistp256@openssh.com"); + public static readonly PublicKeyAlgorithm WebAuthnSkEcdsaSha2Nistp256OpenSSH = new PublicKeyAlgorithm("webauthn-sk-ecdsa-sha2-nistp256@openssh.com"); + public static readonly PublicKeyAlgorithm SshRsaCertV01OpenSSH = new PublicKeyAlgorithm("ssh-rsa-cert-v01@openssh.com"); + public static readonly PublicKeyAlgorithm RsaSha2256CertV01OpenSSH = new PublicKeyAlgorithm("rsa-sha2-256-cert-v01@openssh.com"); + public static readonly PublicKeyAlgorithm RsaSha2512CertV01OpenSSH = new PublicKeyAlgorithm("rsa-sha2-512-cert-v01@openssh.com"); + public static readonly PublicKeyAlgorithm SshDssCertV01OpenSSH = new PublicKeyAlgorithm("ssh-dss-cert-v01@openssh.com"); + public static readonly PublicKeyAlgorithm EcdsaSha2Nistp256CertV01OpenSSH = new PublicKeyAlgorithm("ecdsa-sha2-nistp256-cert-v01@openssh.com"); + public static readonly PublicKeyAlgorithm EcdsaSha2Nistp384CertV01OpenSSH = new PublicKeyAlgorithm("ecdsa-sha2-nistp384-cert-v01@openssh.com"); + public static readonly PublicKeyAlgorithm EcdsaSha2Nistp521CertV01OpenSSH = new PublicKeyAlgorithm("ecdsa-sha2-nistp521-cert-v01@openssh.com"); + public static readonly PublicKeyAlgorithm SkEcdsaSha2Nistp256CertV01OpenSSH = new PublicKeyAlgorithm("sk-ecdsa-sha2-nistp256-cert-v01@openssh.com"); + + public PublicKeyAlgorithm(string name) + { + Name = name; + } + + public string Name { get; } + + public override bool Equals(object? obj) + { + if (obj == null) + { + return false; + } + + if (obj is HostKeyAlgorithm otherHka) + { + return otherHka.Name == Name; + } + + return false; + } + + public override int GetHashCode() + { + return Name.GetHashCode(); + } + + public override string ToString() + { + return Name; + } + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/Renci.SshNet.TestTools.OpenSSH.csproj b/src/Renci.SshNet.TestTools.OpenSSH/Renci.SshNet.TestTools.OpenSSH.csproj new file mode 100644 index 000000000..bd59aa4e5 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/Renci.SshNet.TestTools.OpenSSH.csproj @@ -0,0 +1,21 @@ + + + net7.0 + enable + enable + + + $(NoWarn);CS1591;SYSLIB0021;SYSLIB1045 + + + + + \ No newline at end of file diff --git a/src/Renci.SshNet.TestTools.OpenSSH/SshdConfig.cs b/src/Renci.SshNet.TestTools.OpenSSH/SshdConfig.cs new file mode 100644 index 000000000..b80967113 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/SshdConfig.cs @@ -0,0 +1,501 @@ +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; + +using Renci.SshNet.TestTools.OpenSSH.Formatters; + +namespace Renci.SshNet.TestTools.OpenSSH +{ + public class SshdConfig + { + private static readonly Regex MatchRegex = new Regex($@"\s*Match\s+(User\s+(?[\S]+))?\s*(Address\s+(?[\S]+))?\s*", RegexOptions.Compiled); + + private readonly SubsystemFormatter _subsystemFormatter; + private readonly Int32Formatter _int32Formatter; + private readonly BooleanFormatter _booleanFormatter; + private readonly MatchFormatter _matchFormatter; + + private SshdConfig() + { + AcceptedEnvironmentVariables = new List(); + Ciphers = new List(); + HostKeyFiles = new List(); + HostKeyAlgorithms = new List(); + KeyExchangeAlgorithms = new List(); + PublicKeyAcceptedAlgorithms = new List(); + MessageAuthenticationCodeAlgorithms = new List(); + Subsystems = new List(); + Matches = new List(); + LogLevel = LogLevel.Info; + Port = 22; + Protocol = "2,1"; + + _booleanFormatter = new BooleanFormatter(); + _int32Formatter = new Int32Formatter(); + _matchFormatter = new MatchFormatter(); + _subsystemFormatter = new SubsystemFormatter(); + } + + /// + /// Gets or sets the port number that sshd listens on. + /// + /// + /// The port number that sshd listens on. The default is 22. + /// + public int Port { get; set; } + + /// + /// Gets or sets the list of private host key files used by sshd. + /// + /// + /// A list of private host key files used by sshd. + /// + public List HostKeyFiles { get; set; } + + /// + /// Gets or sets a value specifying whether challenge-response authentication is allowed. + /// + /// + /// A value specifying whether challenge-response authentication is allowed, or + /// if this option is not configured. + /// + public bool? ChallengeResponseAuthentication { get; set; } + + /// + /// Gets or sets a value indicating whether to allow keyboard-interactive authentication. + /// + /// + /// to allow and to disallow keyboard-interactive + /// authentication, or if this option is not configured. + /// + public bool? KeyboardInteractiveAuthentication { get; set; } + + /// + /// Gets or sets the verbosity when logging messages from sshd. + /// + /// + /// The verbosity when logging messages from sshd. The default is . + /// + public LogLevel LogLevel { get; set; } + + /// + /// Gets a sets a value indicating whether the Pluggable Authentication Module interface is enabled. + /// + /// + /// A value indicating whether the Pluggable Authentication Module interface is enabled. + /// + public bool? UsePAM { get; set; } + + public List Subsystems { get; } + + /// + /// Gets a list of conditional blocks. + /// + public List Matches { get; } + + public bool X11Forwarding { get; private set; } + public List AcceptedEnvironmentVariables { get; private set; } + public List Ciphers { get; private set; } + + /// + /// Gets the host key signature algorithms that the server offers. + /// + public List HostKeyAlgorithms { get; private set; } + + /// + /// Gets the available KEX (Key Exchange) algorithms. + /// + public List KeyExchangeAlgorithms { get; private set; } + + /// + /// Gets the signature algorithms that will be accepted for public key authentication. + /// + public List PublicKeyAcceptedAlgorithms { get; private set; } + + /// + /// Gets the available MAC (message authentication code) algorithms. + /// + public List MessageAuthenticationCodeAlgorithms { get; private set; } + + /// + /// Gets a value indicating whether sshd should print /etc/motd when a user logs in interactively. + /// + /// + /// if sshd should print /etc/motd when a user logs in interactively + /// and if it should not; if this option is not configured. + /// + public bool? PrintMotd { get; set; } + + /// + /// Gets or sets the protocol versions sshd supported. + /// + /// + /// The protocol versions sshd supported. The default is 2,1. + /// + public string Protocol { get; set; } + + /// + /// Gets or sets a value indicating whether TCP forwarding is allowed. + /// + /// + /// to allow and to disallow TCP forwarding, + /// or if this option is not configured. + /// + public bool? AllowTcpForwarding { get; set; } + + public void SaveTo(TextWriter writer) + { + writer.WriteLine("Protocol " + Protocol); + writer.WriteLine("Port " + _int32Formatter.Format(Port)); + if (HostKeyFiles.Count > 0) + { + writer.WriteLine("HostKey " + string.Join(",", HostKeyFiles.ToArray())); + } + + if (ChallengeResponseAuthentication is not null) + { + writer.WriteLine("ChallengeResponseAuthentication " + _booleanFormatter.Format(ChallengeResponseAuthentication.Value)); + } + + if (KeyboardInteractiveAuthentication is not null) + { + writer.WriteLine("KbdInteractiveAuthentication " + _booleanFormatter.Format(KeyboardInteractiveAuthentication.Value)); + } + + if (AllowTcpForwarding is not null) + { + writer.WriteLine("AllowTcpForwarding " + _booleanFormatter.Format(AllowTcpForwarding.Value)); + } + + if (PrintMotd is not null) + { + writer.WriteLine("PrintMotd " + _booleanFormatter.Format(PrintMotd.Value)); + } + + writer.WriteLine("LogLevel " + new LogLevelFormatter().Format(LogLevel)); + + foreach (var subsystem in Subsystems) + { + writer.WriteLine("Subsystem " + _subsystemFormatter.Format(subsystem)); + } + + if (UsePAM is not null) + { + writer.WriteLine("UsePAM " + _booleanFormatter.Format(UsePAM.Value)); + } + + writer.WriteLine("X11Forwarding " + _booleanFormatter.Format(X11Forwarding)); + + foreach (var acceptedEnvVar in AcceptedEnvironmentVariables) + { + writer.WriteLine("AcceptEnv " + acceptedEnvVar); + } + + if (Ciphers.Count > 0) + { + writer.WriteLine("Ciphers " + string.Join(",", Ciphers.Select(c => c.Name).ToArray())); + } + + if (HostKeyAlgorithms.Count > 0) + { + writer.WriteLine("HostKeyAlgorithms " + string.Join(",", HostKeyAlgorithms.Select(c => c.Name).ToArray())); + } + + if (KeyExchangeAlgorithms.Count > 0) + { + writer.WriteLine("KexAlgorithms " + string.Join(",", KeyExchangeAlgorithms.Select(c => c.Name).ToArray())); + } + + if (MessageAuthenticationCodeAlgorithms.Count > 0) + { + writer.WriteLine("MACs " + string.Join(",", MessageAuthenticationCodeAlgorithms.Select(c => c.Name).ToArray())); + } + + writer.WriteLine("PubkeyAcceptedAlgorithms " + string.Join(",", PublicKeyAcceptedAlgorithms.Select(c => c.Name).ToArray())); + + foreach (var match in Matches) + { + _matchFormatter.Format(match, writer); + } + } + + public static SshdConfig LoadFrom(Stream stream, Encoding encoding) + { + using (var sr = new StreamReader(stream, encoding)) + { + var sshdConfig = new SshdConfig(); + + Match? currentMatchConfiguration = null; + + string? line; + while ((line = sr.ReadLine()) != null) + { + // Skip empty lines + if (line.Length == 0) + { + continue; + } + + // Skip comments + if (line[0] == '#') + { + continue; + } + + var match = MatchRegex.Match(line); + if (match.Success) + { + var usersGroup = match.Groups["users"]; + var addressesGroup = match.Groups["addresses"]; + var users = usersGroup.Success ? usersGroup.Value.Split(',') : Array.Empty(); + var addresses = addressesGroup.Success ? addressesGroup.Value.Split(',') : Array.Empty(); + + currentMatchConfiguration = new Match(users, addresses); + sshdConfig.Matches.Add(currentMatchConfiguration); + continue; + } + + if (currentMatchConfiguration != null) + { + ProcessMatchOption(currentMatchConfiguration, line); + } + else + { + ProcessGlobalOption(sshdConfig, line); + } + } + + if (sshdConfig.Ciphers == null) + { + // Obtain supported ciphers using ssh -Q cipher + } + + if (sshdConfig.KeyExchangeAlgorithms == null) + { + // Obtain supports key exchange algorithms using ssh -Q kex + } + + if (sshdConfig.HostKeyAlgorithms == null) + { + // Obtain supports host key algorithms using ssh -Q key + } + + if (sshdConfig.MessageAuthenticationCodeAlgorithms == null) + { + // Obtain supported MACs using ssh -Q mac + } + + + return sshdConfig; + } + } + + private static void ProcessGlobalOption(SshdConfig sshdConfig, string line) + { + var matchOptionRegex = new Regex(@"^\s*(?[\S]+)\s+(?.+?){1}\s*$"); + + var optionsMatch = matchOptionRegex.Match(line); + if (!optionsMatch.Success) + { + return; + } + + var nameGroup = optionsMatch.Groups["name"]; + var valueGroup = optionsMatch.Groups["value"]; + + var name = nameGroup.Value; + var value = valueGroup.Value; + + switch (name) + { + case "Port": + sshdConfig.Port = ToInt(value); + break; + case "HostKey": + sshdConfig.HostKeyFiles = ParseCommaSeparatedValue(value); + break; + case "ChallengeResponseAuthentication": + sshdConfig.ChallengeResponseAuthentication = ToBool(value); + break; + case "KbdInteractiveAuthentication": + sshdConfig.KeyboardInteractiveAuthentication = ToBool(value); + break; + case "LogLevel": + sshdConfig.LogLevel = (LogLevel) Enum.Parse(typeof(LogLevel), value, true); + break; + case "Subsystem": + sshdConfig.Subsystems.Add(Subsystem.FromConfig(value)); + break; + case "UsePAM": + sshdConfig.UsePAM = ToBool(value); + break; + case "X11Forwarding": + sshdConfig.X11Forwarding = ToBool(value); + break; + case "Ciphers": + sshdConfig.Ciphers = ParseCiphers(value); + break; + case "KexAlgorithms": + sshdConfig.KeyExchangeAlgorithms = ParseKeyExchangeAlgorithms(value); + break; + case "PubkeyAcceptedAlgorithms": + sshdConfig.PublicKeyAcceptedAlgorithms = ParsePublicKeyAcceptedAlgorithms(value); + break; + case "HostKeyAlgorithms": + sshdConfig.HostKeyAlgorithms = ParseHostKeyAlgorithms(value); + break; + case "MACs": + sshdConfig.MessageAuthenticationCodeAlgorithms = ParseMacs(value); + break; + case "PrintMotd": + sshdConfig.PrintMotd = ToBool(value); + break; + case "AcceptEnv": + ParseAcceptedEnvironmentVariable(sshdConfig, value); + break; + case "Protocol": + sshdConfig.Protocol = value; + break; + case "AllowTcpForwarding": + sshdConfig.AllowTcpForwarding = ToBool(value); + break; + case "KeyRegenerationInterval": + case "HostbasedAuthentication": + case "ServerKeyBits": + case "SyslogFacility": + case "LoginGraceTime": + case "PermitRootLogin": + case "StrictModes": + case "RSAAuthentication": + case "PubkeyAuthentication": + case "IgnoreRhosts": + case "RhostsRSAAuthentication": + case "PermitEmptyPasswords": + case "X11DisplayOffset": + case "PrintLastLog": + case "TCPKeepAlive": + case "AuthorizedKeysFile": + case "PasswordAuthentication": + case "GatewayPorts": + break; + default: + throw new Exception($"Global option '{name}' is not implemented."); + } + } + + private static void ParseAcceptedEnvironmentVariable(SshdConfig sshdConfig, string value) + { + var acceptedEnvironmentVariables = value.Split(' '); + foreach (var acceptedEnvironmentVariable in acceptedEnvironmentVariables) + { + sshdConfig.AcceptedEnvironmentVariables.Add(acceptedEnvironmentVariable); + } + } + + private static List ParseCiphers(string value) + { + var cipherNames = value.Split(','); + var ciphers = new List(cipherNames.Length); + foreach (var cipherName in cipherNames) + { + ciphers.Add(new Cipher(cipherName.Trim())); + } + return ciphers; + } + + private static List ParseKeyExchangeAlgorithms(string value) + { + var kexNames = value.Split(','); + var keyExchangeAlgorithms = new List(kexNames.Length); + foreach (var kexName in kexNames) + { + keyExchangeAlgorithms.Add(new KeyExchangeAlgorithm(kexName.Trim())); + } + return keyExchangeAlgorithms; + } + + public static List ParsePublicKeyAcceptedAlgorithms(string value) + { + var publicKeyAlgorithmNames = value.Split(','); + var publicKeyAlgorithms = new List(publicKeyAlgorithmNames.Length); + foreach (var publicKeyAlgorithmName in publicKeyAlgorithmNames) + { + publicKeyAlgorithms.Add(new PublicKeyAlgorithm(publicKeyAlgorithmName.Trim())); + } + return publicKeyAlgorithms; + } + + private static List ParseHostKeyAlgorithms(string value) + { + var algorithmNames = value.Split(','); + var hostKeyAlgorithms = new List(algorithmNames.Length); + foreach (var algorithmName in algorithmNames) + { + hostKeyAlgorithms.Add(new HostKeyAlgorithm(algorithmName.Trim())); + } + return hostKeyAlgorithms; + } + + private static List ParseMacs(string value) + { + var macNames = value.Split(','); + var macAlgorithms = new List(macNames.Length); + foreach (var algorithmName in macNames) + { + macAlgorithms.Add(new MessageAuthenticationCodeAlgorithm(algorithmName.Trim())); + } + return macAlgorithms; + } + + private static void ProcessMatchOption(Match matchConfiguration, string line) + { + var matchOptionRegex = new Regex(@"^\s+(?[\S]+)\s+(?.+?){1}\s*$"); + + var optionsMatch = matchOptionRegex.Match(line); + if (!optionsMatch.Success) + { + return; + } + + var nameGroup = optionsMatch.Groups["name"]; + var valueGroup = optionsMatch.Groups["value"]; + + var name = nameGroup.Value; + var value = valueGroup.Value; + + switch (name) + { + case "AuthenticationMethods": + matchConfiguration.AuthenticationMethods = value; + break; + default: + throw new Exception($"Match option '{name}' is not implemented."); + } + } + + + private static List ParseCommaSeparatedValue(string value) + { + var values = value.Split(','); + return new List(values); + } + + private static bool ToBool(string value) + { + switch (value) + { + case "yes": + return true; + case "no": + return false; + default: + throw new Exception($"Value '{value}' cannot be mapped to a boolean."); + } + } + + private static int ToInt(string value) + { + return int.Parse(value, NumberFormatInfo.InvariantInfo); + } + } +} diff --git a/src/Renci.SshNet.TestTools.OpenSSH/Subsystem.cs b/src/Renci.SshNet.TestTools.OpenSSH/Subsystem.cs new file mode 100644 index 000000000..4d1155105 --- /dev/null +++ b/src/Renci.SshNet.TestTools.OpenSSH/Subsystem.cs @@ -0,0 +1,41 @@ +using System.Text.RegularExpressions; + +namespace Renci.SshNet.TestTools.OpenSSH +{ + public class Subsystem + { + public Subsystem(string name, string command) + { + Name = name; + Command = command; + } + + public string Name { get; } + + public string Command { get; set; } + + public void WriteTo(TextWriter writer) + { + writer.WriteLine(Name + "=" + Command); + } + + public static Subsystem FromConfig(string value) + { + var subSystemValueRegex = new Regex(@"^\s*(?[\S]+)\s+(?.+?){1}\s*$"); + + var match = subSystemValueRegex.Match(value); + if (match.Success) + { + var nameGroup = match.Groups["name"]; + var commandGroup = match.Groups["command"]; + + var name = nameGroup.Value; + var command = commandGroup.Value; + + return new Subsystem(name, command); + } + + throw new Exception($"'{value}' not recognized as value for Subsystem."); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_HostNameInvalid.cs b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_HostNameInvalid.cs index 068169248..7cd0f6ec6 100644 --- a/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_HostNameInvalid.cs +++ b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_HostNameInvalid.cs @@ -36,7 +36,7 @@ public void ConnectShouldHaveThrownSocketException() { Assert.IsNotNull(_actualException); Assert.IsNull(_actualException.InnerException); - Assert.AreEqual(SocketError.HostNotFound, _actualException.SocketErrorCode); + Assert.IsTrue(_actualException.SocketErrorCode is SocketError.HostNotFound or SocketError.TryAgain); } } } diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyHostInvalid.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyHostInvalid.cs index 59524ffd7..7a87e9d6f 100644 --- a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyHostInvalid.cs +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyHostInvalid.cs @@ -43,7 +43,7 @@ public void ConnectShouldHaveThrownSocketException() { Assert.IsNotNull(_actualException); Assert.IsNull(_actualException.InnerException); - Assert.AreEqual(SocketError.HostNotFound, _actualException.SocketErrorCode); + Assert.IsTrue(_actualException.SocketErrorCode is SocketError.HostNotFound or SocketError.TryAgain); } } } diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingHttpContent.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingHttpContent.cs index 5db6b2353..98ffde6aa 100644 --- a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingHttpContent.cs +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingHttpContent.cs @@ -12,7 +12,6 @@ using Moq; using Renci.SshNet.Common; -using Renci.SshNet.Connection; using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Connection diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingStatusLine.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingStatusLine.cs index 10aa8bd91..bec205f1a 100644 --- a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingStatusLine.cs +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingStatusLine.cs @@ -10,7 +10,6 @@ using Moq; using Renci.SshNet.Common; -using Renci.SshNet.Connection; using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Connection diff --git a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs index 56ad91011..4108c7c09 100644 --- a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs +++ b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Common; + using Renci.SshNet.Connection; using Renci.SshNet.Tests.Common; using System; diff --git a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_NoComments.cs b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_NoComments.cs index 03c1832df..f3ce9ccf8 100644 --- a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_NoComments.cs +++ b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_NoComments.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Common; + using Renci.SshNet.Connection; using Renci.SshNet.Tests.Common; using System; diff --git a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_TerminatedByLineFeedWithoutCarriageReturn.cs b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_TerminatedByLineFeedWithoutCarriageReturn.cs index 7aa26b309..2c35bce53 100644 --- a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_TerminatedByLineFeedWithoutCarriageReturn.cs +++ b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_TerminatedByLineFeedWithoutCarriageReturn.cs @@ -8,7 +8,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Common; using Renci.SshNet.Connection; using Renci.SshNet.Tests.Common; diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionSucceeded.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionSucceeded.cs index b482b753c..5aabb8164 100644 --- a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionSucceeded.cs +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionSucceeded.cs @@ -10,7 +10,6 @@ using Moq; using Renci.SshNet.Common; -using Renci.SshNet.Connection; using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Connection diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress.cs index d8121e894..d87969ced 100644 --- a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress.cs +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress.cs @@ -10,7 +10,6 @@ using Moq; using Renci.SshNet.Common; -using Renci.SshNet.Connection; using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Connection diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyCode.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyCode.cs index 1aaa36e4a..8f6ee9019 100644 --- a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyCode.cs +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyCode.cs @@ -10,7 +10,6 @@ using Moq; using Renci.SshNet.Common; -using Renci.SshNet.Connection; using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Connection diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion.cs index 37b7f8389..4ca6e0c58 100644 --- a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion.cs +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion.cs @@ -1,7 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Renci.SshNet.Common; -using Renci.SshNet.Connection; using Renci.SshNet.Tests.Common; using System; using System.Diagnostics; diff --git a/src/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest.cs b/src/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest.cs index 82459d00a..766dd23a6 100644 --- a/src/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest.cs +++ b/src/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest.cs @@ -1,16 +1,7 @@ using System; -using System.Diagnostics; -#if NET6_0_OR_GREATER -using System.Net.Http; -#else -using System.Net; -#endif // NET6_0_OR_GREATER -using System.Threading; -using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Common; using Renci.SshNet.Tests.Common; using Renci.SshNet.Tests.Properties; @@ -22,92 +13,6 @@ namespace Renci.SshNet.Tests.Classes [TestClass] public partial class ForwardedPortLocalTest : TestBase { - [TestMethod] - [WorkItem(713)] - [Owner("Kenneth_aa")] - [TestCategory("PortForwarding")] - [TestCategory("integration")] - [Description("Test if calling Stop on ForwardedPortLocal instance causes wait.")] - public void Test_PortForwarding_Local_Stop_Hangs_On_Wait() - { - using (var client = new SshClient(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - - var port1 = new ForwardedPortLocal("localhost", 8084, "www.google.com", 80); - client.AddForwardedPort(port1); - port1.Exception += delegate(object sender, ExceptionEventArgs e) - { - Assert.Fail(e.Exception.ToString()); - }; - - port1.Start(); - - var hasTestedTunnel = false; - - _ = ThreadPool.QueueUserWorkItem(delegate(object state) - { - try - { - var url = "http://www.google.com/"; - Debug.WriteLine("Starting web request to \"" + url + "\""); - -#if NET6_0_OR_GREATER - var httpClient = new HttpClient(); - var response = httpClient.GetAsync(url) - .ConfigureAwait(false) - .GetAwaiter() - .GetResult(); -#else - var request = (HttpWebRequest) WebRequest.Create(url); - var response = (HttpWebResponse) request.GetResponse(); -#endif // NET6_0_OR_GREATER - - Assert.IsNotNull(response); - - Debug.WriteLine("Http Response status code: " + response.StatusCode.ToString()); - - response.Dispose(); - - hasTestedTunnel = true; - } - catch (Exception ex) - { - Assert.Fail(ex.ToString()); - } - }); - - // Wait for the web request to complete. - while (!hasTestedTunnel) - { - Thread.Sleep(1000); - } - - try - { - // Try stop the port forwarding, wait 3 seconds and fail if it is still started. - _ = ThreadPool.QueueUserWorkItem(delegate(object state) - { - Debug.WriteLine("Trying to stop port forward."); - port1.Stop(); - Debug.WriteLine("Port forwarding stopped."); - }); - - Thread.Sleep(3000); - if (port1.IsStarted) - { - Assert.Fail("Port forwarding not stopped."); - } - } - catch (Exception ex) - { - Assert.Fail(ex.ToString()); - } - client.Disconnect(); - Debug.WriteLine("Success."); - } - } - [TestMethod] public void ConstructorShouldThrowArgumentNullExceptionWhenBoundHostIsNull() { @@ -177,132 +82,5 @@ public void ConstructorShouldNotThrowExceptionWhenHostIsInvalidDnsName() Assert.AreSame(host, forwardedPort.Host); } - - /// - ///A test for ForwardedPortRemote Constructor - /// - [TestMethod] - [TestCategory("integration")] - public void Test_ForwardedPortRemote() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - #region Example SshClient AddForwardedPort Start Stop ForwardedPortLocal - client.Connect(); - var port = new ForwardedPortLocal(8082, "www.cnn.com", 80); - client.AddForwardedPort(port); - port.Exception += delegate(object sender, ExceptionEventArgs e) - { - Console.WriteLine(e.Exception.ToString()); - }; - port.Start(); - - Thread.Sleep(1000 * 60 * 20); // Wait 20 minutes for port to be forwarded - - port.Stop(); - #endregion - } - Assert.Inconclusive("TODO: Implement code to verify target"); - } - - [TestMethod] - [TestCategory("integration")] - [ExpectedException(typeof(SshConnectionException))] - public void Test_PortForwarding_Local_Without_Connecting() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - var port1 = new ForwardedPortLocal("localhost", 8084, "www.renci.org", 80); - client.AddForwardedPort(port1); - port1.Exception += delegate (object sender, ExceptionEventArgs e) - { - Assert.Fail(e.Exception.ToString()); - }; - port1.Start(); - - _ = Parallel.For(0, - 100, - counter => - { - var start = DateTime.Now; - -#if NET6_0_OR_GREATER - var httpClient = new HttpClient(); - using (var response = httpClient.GetAsync("http://localhost:8084").GetAwaiter().GetResult()) - { - var data = ReadStream(response.Content.ReadAsStream()); -#else - var request = (HttpWebRequest) WebRequest.Create("http://localhost:8084"); - using (var response = (HttpWebResponse) request.GetResponse()) - { - var data = ReadStream(response.GetResponseStream()); -#endif // NET6_0_OR_GREATER - var end = DateTime.Now; - - Debug.WriteLine(string.Format("Request# {2}: Lenght: {0} Time: {1}", data.Length, end - start, counter)); - } - }); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_PortForwarding_Local() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - var port1 = new ForwardedPortLocal("localhost", 8084, "www.renci.org", 80); - client.AddForwardedPort(port1); - port1.Exception += delegate (object sender, ExceptionEventArgs e) - { - Assert.Fail(e.Exception.ToString()); - }; - port1.Start(); - - _ = Parallel.For(0, - 100, - counter => - { - var start = DateTime.Now; - -#if NET6_0_OR_GREATER - var httpClient = new HttpClient(); - using (var response = httpClient.GetAsync("http://localhost:8084").GetAwaiter().GetResult()) - { - var data = ReadStream(response.Content.ReadAsStream()); -#else - var request = (HttpWebRequest) WebRequest.Create("http://localhost:8084"); - using (var response = (HttpWebResponse) request.GetResponse()) - { - var data = ReadStream(response.GetResponseStream()); -#endif // NET6_0_OR_GREATER - var end = DateTime.Now; - - Debug.WriteLine(string.Format("Request# {2}: Length: {0} Time: {1}", data.Length, end - start, counter)); - } - }); - } - } - - private static byte[] ReadStream(System.IO.Stream stream) - { - var buffer = new byte[1024]; - using (var ms = new System.IO.MemoryStream()) - { - while (true) - { - var read = stream.Read(buffer, 0, buffer.Length); - if (read > 0) - { - ms.Write(buffer, 0, read); - } - else - { - return ms.ToArray(); - } - } - } - } } } diff --git a/src/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest.cs b/src/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest.cs index 5f0777190..be6adef7b 100644 --- a/src/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest.cs +++ b/src/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest.cs @@ -1,12 +1,8 @@ using System; -using System.Net; -using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Common; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; namespace Renci.SshNet.Tests.Classes { @@ -16,64 +12,6 @@ namespace Renci.SshNet.Tests.Classes [TestClass] public partial class ForwardedPortRemoteTest : TestBase { - [TestMethod] - [Description("Test passing null to AddForwardedPort hosts (remote).")] - [ExpectedException(typeof(ArgumentNullException))] - [TestCategory("integration")] - public void Test_AddForwardedPort_Remote_Hosts_Are_Null() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - var port1 = new ForwardedPortRemote(boundHost: null, 8080, host: null, 80); - client.AddForwardedPort(port1); - client.Disconnect(); - } - } - - [TestMethod] - [Description("Test passing invalid port numbers to AddForwardedPort.")] - [ExpectedException(typeof(ArgumentOutOfRangeException))] - [TestCategory("integration")] - public void Test_AddForwardedPort_Invalid_PortNumber() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - var port1 = new ForwardedPortRemote("localhost", IPEndPoint.MaxPort + 1, "www.renci.org", IPEndPoint.MaxPort + 1); - client.AddForwardedPort(port1); - client.Disconnect(); - } - } - - /// - ///A test for ForwardedPortRemote Constructor - /// - [TestMethod] - [TestCategory("integration")] - public void Test_ForwardedPortRemote() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - #region Example SshClient AddForwardedPort Start Stop ForwardedPortRemote - client.Connect(); - var port = new ForwardedPortRemote(8082, "www.cnn.com", 80); - client.AddForwardedPort(port); - port.Exception += delegate(object sender, ExceptionEventArgs e) - { - Console.WriteLine(e.Exception.ToString()); - }; - port.Start(); - - Thread.Sleep(1000 * 60 * 20); // Wait 20 minutes for port to be forwarded - - port.Stop(); - #endregion - } - Assert.Inconclusive("TODO: Implement code to verify target"); - } - - /// ///A test for Stop /// @@ -152,46 +90,5 @@ public void ForwardedPortRemoteConstructorTest1() var target = new ForwardedPortRemote(boundPort, host, port); Assert.Inconclusive("TODO: Implement code to verify target"); } - -#if FEATURE_TPL - [TestMethod] - [TestCategory("integration")] - public void Test_PortForwarding_Remote() - { - // ****************************************************************** - // ************* Tests are still in not finished ******************** - // ****************************************************************** - - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - var port1 = new ForwardedPortRemote(8082, "www.renci.org", 80); - client.AddForwardedPort(port1); - port1.Exception += delegate (object sender, ExceptionEventArgs e) - { - Assert.Fail(e.Exception.ToString()); - }; - port1.Start(); - var boundport = port1.BoundPort; - - System.Threading.Tasks.Parallel.For(0, 5, - - //new ParallelOptions - //{ - // MaxDegreeOfParallelism = 1, - //}, - (counter) => - { - var cmd = client.CreateCommand(string.Format("wget -O- http://localhost:{0}", boundport)); - var result = cmd.Execute(); - var end = DateTime.Now; - System.Diagnostics.Debug.WriteLine(string.Format("Length: {0}", result.Length)); - } - ); - Thread.Sleep(1000 * 100); - port1.Stop(); - } - } -#endif // FEATURE_TPL } } diff --git a/src/Renci.SshNet.Tests/Classes/KeyboardInteractiveConnectionInfoTest.cs b/src/Renci.SshNet.Tests/Classes/KeyboardInteractiveConnectionInfoTest.cs index 0fc12c462..f50aaf9b7 100644 --- a/src/Renci.SshNet.Tests/Classes/KeyboardInteractiveConnectionInfoTest.cs +++ b/src/Renci.SshNet.Tests/Classes/KeyboardInteractiveConnectionInfoTest.cs @@ -1,8 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Common; + using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; -using System; namespace Renci.SshNet.Tests.Classes { @@ -12,40 +10,6 @@ namespace Renci.SshNet.Tests.Classes [TestClass] public class KeyboardInteractiveConnectionInfoTest : TestBase { - [TestMethod] - [TestCategory("KeyboardInteractiveConnectionInfo")] - [TestCategory("integration")] - public void Test_KeyboardInteractiveConnectionInfo() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - - #region Example KeyboardInteractiveConnectionInfo AuthenticationPrompt - var connectionInfo = new KeyboardInteractiveConnectionInfo(host, username); - connectionInfo.AuthenticationPrompt += delegate(object sender, AuthenticationPromptEventArgs e) - { - System.Console.WriteLine(e.Instruction); - - foreach (var prompt in e.Prompts) - { - Console.WriteLine(prompt.Request); - prompt.Response = Console.ReadLine(); - } - }; - - using (var client = new SftpClient(connectionInfo)) - { - client.Connect(); - // Do something here - client.Disconnect(); - } - #endregion - - Assert.AreEqual(connectionInfo.Host, Resources.HOST); - Assert.AreEqual(connectionInfo.Username, Resources.USERNAME); - } - /// ///A test for Dispose /// @@ -192,4 +156,4 @@ public void KeyboardInteractiveConnectionInfoConstructorTest7() Assert.Inconclusive("TODO: Implement code to verify target"); } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.Tests/Classes/PasswordAuthenticationMethodTest.cs b/src/Renci.SshNet.Tests/Classes/PasswordAuthenticationMethodTest.cs index ec0d3fef8..951314d1c 100644 --- a/src/Renci.SshNet.Tests/Classes/PasswordAuthenticationMethodTest.cs +++ b/src/Renci.SshNet.Tests/Classes/PasswordAuthenticationMethodTest.cs @@ -1,6 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; using System; namespace Renci.SshNet.Tests.Classes @@ -59,32 +58,6 @@ public void Password_Test_Pass_Valid() new PasswordAuthenticationMethod("valid", string.Empty); } - [TestMethod] - [WorkItem(1140)] - [TestCategory("BaseClient")] - [TestCategory("integration")] - [Description("Test whether IsConnected is false after disconnect.")] - [Owner("Kenneth_aa")] - public void Test_BaseClient_IsConnected_True_After_Disconnect() - { - // 2012-04-29 - Kenneth_aa - // The problem with this test, is that after SSH Net calls .Disconnect(), the library doesn't wait - // for the server to confirm disconnect before IsConnected is checked. And now I'm not mentioning - // anything about Socket's either. - - var connectionInfo = new PasswordAuthenticationMethod(Resources.USERNAME, Resources.PASSWORD); - - using (SftpClient client = new SftpClient(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - Assert.AreEqual(true, client.IsConnected, "IsConnected is not true after Connect() was called."); - - client.Disconnect(); - - Assert.AreEqual(false, client.IsConnected, "IsConnected is true after Disconnect() was called."); - } - } - /// ///A test for Name /// @@ -158,4 +131,4 @@ public void PasswordAuthenticationMethodConstructorTest1() Assert.Inconclusive("TODO: Implement code to verify target"); } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.Tests/Classes/PasswordConnectionInfoTest.cs b/src/Renci.SshNet.Tests/Classes/PasswordConnectionInfoTest.cs index 824f649f1..56a510140 100644 --- a/src/Renci.SshNet.Tests/Classes/PasswordConnectionInfoTest.cs +++ b/src/Renci.SshNet.Tests/Classes/PasswordConnectionInfoTest.cs @@ -1,5 +1,4 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Common; using Renci.SshNet.Tests.Common; using Renci.SshNet.Tests.Properties; using System; @@ -13,84 +12,6 @@ namespace Renci.SshNet.Tests.Classes [TestClass] public class PasswordConnectionInfoTest : TestBase { - [TestMethod] - [TestCategory("PasswordConnectionInfo")] - [TestCategory("integration")] - public void Test_PasswordConnectionInfo() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - - #region Example PasswordConnectionInfo - var connectionInfo = new PasswordConnectionInfo(host, username, password); - using (var client = new SftpClient(connectionInfo)) - { - client.Connect(); - // Do something here - client.Disconnect(); - } - #endregion - - Assert.AreEqual(connectionInfo.Host, Resources.HOST); - Assert.AreEqual(connectionInfo.Username, Resources.USERNAME); - } - - [TestMethod] - [TestCategory("PasswordConnectionInfo")] - [TestCategory("integration")] - public void Test_PasswordConnectionInfo_PasswordExpired() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - - #region Example PasswordConnectionInfo PasswordExpired - var connectionInfo = new PasswordConnectionInfo("host", "username", "password"); - var encoding = SshData.Ascii; - connectionInfo.PasswordExpired += delegate(object sender, AuthenticationPasswordChangeEventArgs e) - { - e.NewPassword = encoding.GetBytes("123456"); - }; - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - - client.Disconnect(); - } - #endregion - - Assert.Inconclusive(); - } - [TestMethod] - [TestCategory("PasswordConnectionInfo")] - [TestCategory("integration")] - public void Test_PasswordConnectionInfo_AuthenticationBanner() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - - #region Example PasswordConnectionInfo AuthenticationBanner - var connectionInfo = new PasswordConnectionInfo(host, username, password); - connectionInfo.AuthenticationBanner += delegate(object sender, AuthenticationBannerEventArgs e) - { - Console.WriteLine(e.BannerMessage); - }; - using (var client = new SftpClient(connectionInfo)) - { - client.Connect(); - // Do something here - client.Disconnect(); - } - #endregion - - Assert.AreEqual(connectionInfo.Host, Resources.HOST); - Assert.AreEqual(connectionInfo.Username, Resources.USERNAME); - } - - [WorkItem(703), TestMethod] [TestCategory("PasswordConnectionInfo")] public void Test_ConnectionInfo_Host_Is_Null() @@ -148,60 +69,7 @@ public void Test_ConnectionInfo_BigPortNumber() { _ = new PasswordConnectionInfo(Resources.HOST, IPEndPoint.MaxPort + 1, Resources.USERNAME, Resources.PASSWORD); } - - [TestMethod] - [Owner("Kenneth_aa")] - [Description("Test connect to remote server via a SOCKS4 proxy server.")] - [TestCategory("Proxy")] - [TestCategory("integration")] - public void Test_Ssh_Connect_Via_Socks4() - { - var connInfo = new PasswordConnectionInfo(Resources.HOST, Resources.USERNAME, Resources.PASSWORD, ProxyTypes.Socks4, Resources.PROXY_HOST, int.Parse(Resources.PROXY_PORT)); - using (var client = new SshClient(connInfo)) - { - client.Connect(); - - var ret = client.RunCommand("ls -la"); - - client.Disconnect(); - } - } - - [TestMethod] - [Owner("Kenneth_aa")] - [Description("Test connect to remote server via a TCP SOCKS5 proxy server.")] - [TestCategory("Proxy")] - [TestCategory("integration")] - public void Test_Ssh_Connect_Via_TcpSocks5() - { - var connInfo = new PasswordConnectionInfo(Resources.HOST, Resources.USERNAME, Resources.PASSWORD, ProxyTypes.Socks5, Resources.PROXY_HOST, int.Parse(Resources.PROXY_PORT)); - using (var client = new SshClient(connInfo)) - { - client.Connect(); - - var ret = client.RunCommand("ls -la"); - client.Disconnect(); - } - } - - [TestMethod] - [Owner("Kenneth_aa")] - [Description("Test connect to remote server via a HTTP proxy server.")] - [TestCategory("Proxy")] - [TestCategory("integration")] - public void Test_Ssh_Connect_Via_HttpProxy() - { - var connInfo = new PasswordConnectionInfo(Resources.HOST, Resources.USERNAME, Resources.PASSWORD, ProxyTypes.Http, Resources.PROXY_HOST, int.Parse(Resources.PROXY_PORT)); - using (var client = new SshClient(connInfo)) - { - client.Connect(); - - var ret = client.RunCommand("ls -la"); - - client.Disconnect(); - } - } - + /// ///A test for Dispose /// diff --git a/src/Renci.SshNet.Tests/Classes/PrivateKeyConnectionInfoTest.cs b/src/Renci.SshNet.Tests/Classes/PrivateKeyConnectionInfoTest.cs index f8c863ba0..40a781174 100644 --- a/src/Renci.SshNet.Tests/Classes/PrivateKeyConnectionInfoTest.cs +++ b/src/Renci.SshNet.Tests/Classes/PrivateKeyConnectionInfoTest.cs @@ -1,8 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; -using System.IO; -using System.Text; namespace Renci.SshNet.Tests.Classes { @@ -12,53 +9,6 @@ namespace Renci.SshNet.Tests.Classes [TestClass] public class PrivateKeyConnectionInfoTest : TestBase { - [TestMethod] - [TestCategory("PrivateKeyConnectionInfo")] - [TestCategory("integration")] - public void Test_PrivateKeyConnectionInfo() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - MemoryStream keyFileStream = new MemoryStream(Encoding.ASCII.GetBytes(Resources.RSA_KEY_WITHOUT_PASS)); - - #region Example PrivateKeyConnectionInfo PrivateKeyFile - var connectionInfo = new PrivateKeyConnectionInfo(host, username, new PrivateKeyFile(keyFileStream)); - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - #endregion - - Assert.AreEqual(connectionInfo.Host, Resources.HOST); - Assert.AreEqual(connectionInfo.Username, Resources.USERNAME); - } - - [TestMethod] - [TestCategory("PrivateKeyConnectionInfo")] - [TestCategory("integration")] - public void Test_PrivateKeyConnectionInfo_MultiplePrivateKey() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - MemoryStream keyFileStream1 = new MemoryStream(Encoding.ASCII.GetBytes(Resources.RSA_KEY_WITHOUT_PASS)); - MemoryStream keyFileStream2 = new MemoryStream(Encoding.ASCII.GetBytes(Resources.RSA_KEY_WITHOUT_PASS)); - - #region Example PrivateKeyConnectionInfo PrivateKeyFile Multiple - var connectionInfo = new PrivateKeyConnectionInfo(host, username, - new PrivateKeyFile(keyFileStream1), - new PrivateKeyFile(keyFileStream2)); - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - #endregion - - Assert.AreEqual(connectionInfo.Host, Resources.HOST); - Assert.AreEqual(connectionInfo.Username, Resources.USERNAME); - } - /// ///A test for Dispose /// @@ -214,4 +164,4 @@ public void PrivateKeyConnectionInfoConstructorTest7() Assert.Inconclusive("TODO: Implement code to verify target"); } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest.cs index a9af0c3dd..bcdf93966 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTest.cs @@ -1,15 +1,11 @@ using System; using System.IO; -using System.Linq; -using System.Security.Cryptography; using System.Text; -using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Common; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; namespace Renci.SshNet.Tests.Classes { @@ -219,203 +215,6 @@ public void RemotePathTransformation_Value_Null() Assert.AreSame(RemotePathTransformation.ShellQuote, client.RemotePathTransformation); } - [TestMethod] - [TestCategory("Scp")] - [TestCategory("integration")] - public void Test_Scp_File_Upload_Download() - { - RemoveAllFiles(); - - using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - scp.Connect(); - - var uploadedFileName = Path.GetTempFileName(); - var downloadedFileName = Path.GetTempFileName(); - - CreateTestFile(uploadedFileName, 1); - - scp.Upload(new FileInfo(uploadedFileName), Path.GetFileName(uploadedFileName)); - - scp.Download(Path.GetFileName(uploadedFileName), new FileInfo(downloadedFileName)); - - // Calculate MD5 value - var uploadedHash = CalculateMD5(uploadedFileName); - var downloadedHash = CalculateMD5(downloadedFileName); - - File.Delete(uploadedFileName); - File.Delete(downloadedFileName); - - scp.Disconnect(); - - Assert.AreEqual(uploadedHash, downloadedHash); - } - } - - [TestMethod] - [TestCategory("Scp")] - [TestCategory("integration")] - public void Test_Scp_Stream_Upload_Download() - { - RemoveAllFiles(); - - using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - scp.Connect(); - - var uploadedFileName = Path.GetTempFileName(); - var downloadedFileName = Path.GetTempFileName(); - - CreateTestFile(uploadedFileName, 1); - - // Calculate has value - using (var stream = File.OpenRead(uploadedFileName)) - { - scp.Upload(stream, Path.GetFileName(uploadedFileName)); - } - - using (var stream = File.OpenWrite(downloadedFileName)) - { - scp.Download(Path.GetFileName(uploadedFileName), stream); - } - - // Calculate MD5 value - var uploadedHash = CalculateMD5(uploadedFileName); - var downloadedHash = CalculateMD5(downloadedFileName); - - File.Delete(uploadedFileName); - File.Delete(downloadedFileName); - - scp.Disconnect(); - - Assert.AreEqual(uploadedHash, downloadedHash); - } - } - - [TestMethod] - [TestCategory("Scp")] - [TestCategory("integration")] - public void Test_Scp_10MB_File_Upload_Download() - { - RemoveAllFiles(); - - using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - scp.Connect(); - - var uploadedFileName = Path.GetTempFileName(); - var downloadedFileName = Path.GetTempFileName(); - - CreateTestFile(uploadedFileName, 10); - - scp.Upload(new FileInfo(uploadedFileName), Path.GetFileName(uploadedFileName)); - - scp.Download(Path.GetFileName(uploadedFileName), new FileInfo(downloadedFileName)); - - // Calculate MD5 value - var uploadedHash = CalculateMD5(uploadedFileName); - var downloadedHash = CalculateMD5(downloadedFileName); - - File.Delete(uploadedFileName); - File.Delete(downloadedFileName); - - scp.Disconnect(); - - Assert.AreEqual(uploadedHash, downloadedHash); - } - } - - [TestMethod] - [TestCategory("Scp")] - [TestCategory("integration")] - public void Test_Scp_10MB_Stream_Upload_Download() - { - RemoveAllFiles(); - - using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - scp.Connect(); - - var uploadedFileName = Path.GetTempFileName(); - var downloadedFileName = Path.GetTempFileName(); - - CreateTestFile(uploadedFileName, 10); - - // Calculate has value - using (var stream = File.OpenRead(uploadedFileName)) - { - scp.Upload(stream, Path.GetFileName(uploadedFileName)); - } - - using (var stream = File.OpenWrite(downloadedFileName)) - { - scp.Download(Path.GetFileName(uploadedFileName), stream); - } - - // Calculate MD5 value - var uploadedHash = CalculateMD5(uploadedFileName); - var downloadedHash = CalculateMD5(downloadedFileName); - - File.Delete(uploadedFileName); - File.Delete(downloadedFileName); - - scp.Disconnect(); - - Assert.AreEqual(uploadedHash, downloadedHash); - } - } - - [TestMethod] - [TestCategory("Scp")] - [TestCategory("integration")] - public void Test_Scp_Directory_Upload_Download() - { - RemoveAllFiles(); - - using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - scp.Connect(); - - var uploadDirectory = - Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName())); - for (var i = 0; i < 3; i++) - { - var subfolder = Directory.CreateDirectory(string.Format(@"{0}\folder_{1}", uploadDirectory.FullName, i)); - - for (var j = 0; j < 5; j++) - { - CreateTestFile(string.Format(@"{0}\file_{1}", subfolder.FullName, j), 1); - } - - CreateTestFile(string.Format(@"{0}\file_{1}", uploadDirectory.FullName, i), 1); - } - - scp.Upload(uploadDirectory, "uploaded_dir"); - - var downloadDirectory = - Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName())); - - scp.Download("uploaded_dir", downloadDirectory); - - var uploadedFiles = uploadDirectory.GetFiles("*.*", SearchOption.AllDirectories); - var downloadFiles = downloadDirectory.GetFiles("*.*", SearchOption.AllDirectories); - - var result = from f1 in uploadedFiles - from f2 in downloadFiles - where - f1.FullName.Substring(uploadDirectory.FullName.Length) == - f2.FullName.Substring(downloadDirectory.FullName.Length) - && CalculateMD5(f1.FullName) == CalculateMD5(f2.FullName) - select f1; - - var counter = result.Count(); - - scp.Disconnect(); - - Assert.IsTrue(counter == uploadedFiles.Length && uploadedFiles.Length == downloadFiles.Length); - } - } - /// ///A test for OperationTimeout /// @@ -539,133 +338,6 @@ public void DownloadTest2() Assert.Inconclusive("A method that does not return a value cannot be verified."); } - [TestMethod] - [TestCategory("Scp")] - [TestCategory("integration")] - public void Test_Scp_File_20_Parallel_Upload_Download() - { - using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - scp.Connect(); - - var uploadFilenames = new string[20]; - for (var i = 0; i < uploadFilenames.Length; i++) - { - uploadFilenames[i] = Path.GetTempFileName(); - CreateTestFile(uploadFilenames[i], 1); - } - - _ = Parallel.ForEach(uploadFilenames, - filename => - { - scp.Upload(new FileInfo(filename), Path.GetFileName(filename)); - }); - _ = Parallel.ForEach(uploadFilenames, - filename => - { - scp.Download(Path.GetFileName(filename), new FileInfo(string.Format("{0}.down", filename))); - }); - - var result = from file in uploadFilenames - where CalculateMD5(file) == CalculateMD5(string.Format("{0}.down", file)) - select file; - - scp.Disconnect(); - - Assert.IsTrue(result.Count() == uploadFilenames.Length); - } - } - - [TestMethod] - [TestCategory("Scp")] - [TestCategory("integration")] - public void Test_Scp_File_Upload_Download_Events() - { - using (var scp = new ScpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - scp.Connect(); - - var uploadFilenames = new string[10]; - - for (var i = 0; i < uploadFilenames.Length; i++) - { - uploadFilenames[i] = Path.GetTempFileName(); - CreateTestFile(uploadFilenames[i], 1); - } - - var uploadedFiles = uploadFilenames.ToDictionary(Path.GetFileName, (filename) => 0L); - var downloadedFiles = uploadFilenames.ToDictionary((filename) => string.Format("{0}.down", Path.GetFileName(filename)), (filename) => 0L); - - scp.Uploading += delegate (object sender, ScpUploadEventArgs e) - { - uploadedFiles[e.Filename] = e.Uploaded; - }; - - scp.Downloading += delegate (object sender, ScpDownloadEventArgs e) - { - downloadedFiles[string.Format("{0}.down", e.Filename)] = e.Downloaded; - }; - - _ = Parallel.ForEach(uploadFilenames, - filename => - { - scp.Upload(new FileInfo(filename), Path.GetFileName(filename)); - }); - _ = Parallel.ForEach(uploadFilenames, - filename => - { - scp.Download(Path.GetFileName(filename), new FileInfo(string.Format("{0}.down", filename))); - }); - - var result = from uf in uploadedFiles - from df in downloadedFiles - where string.Format("{0}.down", uf.Key) == df.Key && uf.Value == df.Value - select uf; - - scp.Disconnect(); - - Assert.IsTrue(result.Count() == uploadFilenames.Length && uploadFilenames.Length == uploadedFiles.Count && uploadedFiles.Count == downloadedFiles.Count); - } - } - - protected static string CalculateMD5(string fileName) - { - using (var file = new FileStream(fileName, FileMode.Open)) - { -#if NET7_0_OR_GREATER - var hash = MD5.HashData(file); -#else -#if NET6_0 - var md5 = MD5.Create(); -#else - MD5 md5 = new MD5CryptoServiceProvider(); -#endif // NET6_0 - var hash = md5.ComputeHash(file); -#endif // NET7_0_OR_GREATER - - file.Close(); - - var sb = new StringBuilder(); - - for (var i = 0; i < hash.Length; i++) - { - _ = sb.Append(i.ToString("x2")); - } - - return sb.ToString(); - } - } - - private static void RemoveAllFiles() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - _ = client.RunCommand("rm -rf *"); - client.Disconnect(); - } - } - private PrivateKeyFile GetRsaKey() { using (var stream = GetData("Key.RSA.txt")) diff --git a/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.cs b/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.cs index ef6b69197..03a2e34e9 100644 --- a/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.cs +++ b/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.cs @@ -3,7 +3,6 @@ using Renci.SshNet.Security.Cryptography.Ciphers; using Renci.SshNet.Security.Cryptography.Ciphers.Modes; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers { @@ -84,125 +83,6 @@ public void Decrypt_InputAndOffsetAndLength_128_CTR() Assert.IsTrue(expected.IsEqualTo(actual)); } - [TestMethod] - [Owner("olegkap")] - [TestCategory("Cipher")] - [TestCategory("integration")] - public void Test_Cipher_AEes128CBC_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.Encryptions.Clear(); - connectionInfo.Encryptions.Add("aes128-cbc", new CipherInfo(128, (key, iv) => { return new AesCipher(key, new CbcCipherMode(iv), null); })); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [Owner("olegkap")] - [TestCategory("Cipher")] - [TestCategory("integration")] - public void Test_Cipher_Aes192CBC_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.Encryptions.Clear(); - connectionInfo.Encryptions.Add("aes192-cbc", new CipherInfo(192, (key, iv) => { return new AesCipher(key, new CbcCipherMode(iv), null); })); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [Owner("olegkap")] - [TestCategory("Cipher")] - [TestCategory("integration")] - public void Test_Cipher_Aes256CBC_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.Encryptions.Clear(); - connectionInfo.Encryptions.Add("aes256-cbc", new CipherInfo(256, (key, iv) => { return new AesCipher(key, new CbcCipherMode(iv), null); })); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [Owner("olegkap")] - [TestCategory("Cipher")] - [TestCategory("integration")] - public void Test_Cipher_Aes128CTR_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.Encryptions.Clear(); - connectionInfo.Encryptions.Add("aes128-ctr", new CipherInfo(128, (key, iv) => { return new AesCipher(key, new CtrCipherMode(iv), null); })); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [Owner("olegkap")] - [TestCategory("Cipher")] - [TestCategory("integration")] - public void Test_Cipher_Aes192CTR_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.Encryptions.Clear(); - connectionInfo.Encryptions.Add("aes192-ctr", new CipherInfo(192, (key, iv) => { return new AesCipher(key, new CtrCipherMode(iv), null); })); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [Owner("olegkap")] - [TestCategory("Cipher")] - [TestCategory("integration")] - public void Test_Cipher_Aes256CTR_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.Encryptions.Clear(); - connectionInfo.Encryptions.Add("aes256-ctr", new CipherInfo(256, (key, iv) => { return new AesCipher(key, new CtrCipherMode(iv), null); })); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [Owner("olegkap")] - [TestCategory("Cipher")] - [TestCategory("integration")] - public void Test_Cipher_Arcfour_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.Encryptions.Clear(); - connectionInfo.Encryptions.Add("arcfour", new CipherInfo(128, (key, iv) => { return new Arc4Cipher(key, false); })); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - /// ///A test for DecryptBlock /// @@ -263,4 +143,4 @@ public void EncryptBlockTest() Assert.Inconclusive("Verify the correctness of this test method."); } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/Arc4CipherTest.cs b/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/Arc4CipherTest.cs index f83aa7aa5..5b972021b 100644 --- a/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/Arc4CipherTest.cs +++ b/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/Arc4CipherTest.cs @@ -3,7 +3,6 @@ using Renci.SshNet.Common; using Renci.SshNet.Security.Cryptography.Ciphers; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers { @@ -156,40 +155,5 @@ public void EncryptBlockTest() Assert.AreEqual(expected, actual); Assert.Inconclusive("Verify the correctness of this test method."); } - - [TestMethod] - [Owner("olegkap")] - [TestCategory("Cipher")] - [TestCategory("integration")] - public void Test_Cipher_Arcfour128_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.Encryptions.Clear(); - connectionInfo.Encryptions.Add("arcfour128", new CipherInfo(128, (key, iv) => { return new Arc4Cipher(key, true); })); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [Owner("olegkap")] - [TestCategory("Cipher")] - [TestCategory("integration")] - public void Test_Cipher_Arcfour256_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.Encryptions.Clear(); - connectionInfo.Encryptions.Add("arcfour256", new CipherInfo(256, (key, iv) => { return new Arc4Cipher(key, true); })); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/BlowfishCipherTest.cs b/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/BlowfishCipherTest.cs index 1c2d0aec8..168b1eaf8 100644 --- a/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/BlowfishCipherTest.cs +++ b/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/BlowfishCipherTest.cs @@ -2,7 +2,7 @@ using Renci.SshNet.Security.Cryptography.Ciphers; using Renci.SshNet.Security.Cryptography.Ciphers.Modes; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; + using System.Linq; namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers @@ -29,23 +29,6 @@ public void Test_Cipher_Blowfish_128_CBC() } } - [TestMethod] - [Owner("olegkap")] - [TestCategory("Cipher")] - [TestCategory("integration")] - public void Test_Cipher_BlowfishCBC_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.Encryptions.Clear(); - connectionInfo.Encryptions.Add("blowfish-cbc", new CipherInfo(128, (key, iv) => { return new BlowfishCipher(key, new CbcCipherMode(iv), null); })); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - /// ///A test for BlowfishCipher Constructor /// diff --git a/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/CastCipherTest.cs b/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/CastCipherTest.cs index c077a3098..a04c5f2db 100644 --- a/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/CastCipherTest.cs +++ b/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/CastCipherTest.cs @@ -2,7 +2,7 @@ using Renci.SshNet.Security.Cryptography.Ciphers; using Renci.SshNet.Security.Cryptography.Ciphers.Modes; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; + using System.Linq; namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers @@ -39,22 +39,6 @@ public void Decrypt_128_CBC() Assert.IsTrue(r.SequenceEqual(input)); } - [TestMethod] - [Owner("olegkap")] - [TestCategory("Cipher")] - [TestCategory("integration")] - public void Test_Cipher_Cast128CBC_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.Encryptions.Clear(); - connectionInfo.Encryptions.Add("cast128-cbc", new CipherInfo(128, (key, iv) => { return new CastCipher(key, new CbcCipherMode(iv), null); })); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } /// ///A test for CastCipher Constructor /// @@ -115,4 +99,4 @@ public void EncryptBlockTest() Assert.Inconclusive("Verify the correctness of this test method."); } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.cs b/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.cs index 5b4f97bac..9dc6a2b65 100644 --- a/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.cs +++ b/src/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.cs @@ -5,8 +5,6 @@ using Renci.SshNet.Security.Cryptography.Ciphers; using Renci.SshNet.Security.Cryptography.Ciphers.Modes; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; - namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers { @@ -32,22 +30,6 @@ public void Test_Cipher_3DES_CBC() } } - [TestMethod] - [Owner("olegkap")] - [TestCategory("Cipher")] - [TestCategory("integration")] - public void Test_Cipher_TripleDESCBC_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.Encryptions.Clear(); - connectionInfo.Encryptions.Add("3des-cbc", new CipherInfo(192, (key, iv) => { return new TripleDesCipher(key, new CbcCipherMode(iv), null); })); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } /// ///A test for TripleDesCipher Constructor /// diff --git a/src/Renci.SshNet.Tests/Classes/Security/Cryptography/HMacTest.cs b/src/Renci.SshNet.Tests/Classes/Security/Cryptography/HMacTest.cs deleted file mode 100644 index 94e558b2a..000000000 --- a/src/Renci.SshNet.Tests/Classes/Security/Cryptography/HMacTest.cs +++ /dev/null @@ -1,134 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; -using Renci.SshNet.Abstractions; - -namespace Renci.SshNet.Tests.Classes.Security.Cryptography -{ - /// - /// Provides HMAC algorithm implementation. - /// - [TestClass] - public class HMacTest : TestBase - { - [TestMethod] - [TestCategory("integration")] - public void Test_HMac_MD5_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.HmacAlgorithms.Clear(); - connectionInfo.HmacAlgorithms.Add("hmac-md5", new HashInfo(16 * 8, CryptoAbstraction.CreateHMACMD5)); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_HMac_Sha1_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.HmacAlgorithms.Clear(); - connectionInfo.HmacAlgorithms.Add("hmac-sha1", new HashInfo(20 * 8, CryptoAbstraction.CreateHMACSHA1)); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_HMac_MD5_96_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.HmacAlgorithms.Clear(); - connectionInfo.HmacAlgorithms.Add("hmac-md5", new HashInfo(16 * 8, key => CryptoAbstraction.CreateHMACMD5(key, 96))); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_HMac_Sha1_96_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.HmacAlgorithms.Clear(); - connectionInfo.HmacAlgorithms.Add("hmac-sha1", new HashInfo(20 * 8, key => CryptoAbstraction.CreateHMACSHA1(key, 96))); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_HMac_Sha256_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.HmacAlgorithms.Clear(); - connectionInfo.HmacAlgorithms.Add("hmac-sha2-256", new HashInfo(32 * 8, CryptoAbstraction.CreateHMACSHA256)); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_HMac_Sha256_96_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.HmacAlgorithms.Clear(); - connectionInfo.HmacAlgorithms.Add("hmac-sha2-256-96", new HashInfo(32 * 8, (key) => CryptoAbstraction.CreateHMACSHA256(key, 96))); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_HMac_RIPEMD160_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.HmacAlgorithms.Clear(); - connectionInfo.HmacAlgorithms.Add("hmac-ripemd160", new HashInfo(160, CryptoAbstraction.CreateHMACRIPEMD160)); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_HMac_RIPEMD160_OPENSSH_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.HmacAlgorithms.Clear(); - connectionInfo.HmacAlgorithms.Add("hmac-ripemd160@openssh.com", new HashInfo(160, CryptoAbstraction.CreateHMACRIPEMD160)); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - } -} \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Classes/Security/KeyHostAlgorithmTest.cs b/src/Renci.SshNet.Tests/Classes/Security/KeyHostAlgorithmTest.cs deleted file mode 100644 index 9bd5d10c3..000000000 --- a/src/Renci.SshNet.Tests/Classes/Security/KeyHostAlgorithmTest.cs +++ /dev/null @@ -1,123 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Security; -using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; - -namespace Renci.SshNet.Tests.Classes.Security -{ - /// - /// Implements key support for host algorithm. - /// - [TestClass] - public class KeyHostAlgorithmTest : TestBase - { - [TestMethod] - [TestCategory("integration")] - public void Test_HostKey_SshRsa_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.HostKeyAlgorithms.Clear(); - connectionInfo.HostKeyAlgorithms.Add("ssh-rsa", (data) => { return new KeyHostAlgorithm("ssh-rsa", new RsaKey(), data); }); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_HostKey_SshDss_Connection() - { - var connectionInfo = new PasswordConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD); - connectionInfo.HostKeyAlgorithms.Clear(); - connectionInfo.HostKeyAlgorithms.Add("ssh-dss", (data) => { return new KeyHostAlgorithm("ssh-dss", new DsaKey(), data); }); - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - client.Disconnect(); - } - } - - /// - ///A test for KeyHostAlgorithm Constructor - /// - [TestMethod] - [Ignore] // placeholder for actual test - public void KeyHostAlgorithmConstructorTest() - { - string name = string.Empty; // TODO: Initialize to an appropriate value - Key key = null; // TODO: Initialize to an appropriate value - KeyHostAlgorithm target = new KeyHostAlgorithm(name, key); - Assert.Inconclusive("TODO: Implement code to verify target"); - } - - /// - ///A test for KeyHostAlgorithm Constructor - /// - [TestMethod] - [Ignore] // placeholder for actual test - public void KeyHostAlgorithmConstructorTest1() - { - string name = string.Empty; // TODO: Initialize to an appropriate value - Key key = null; // TODO: Initialize to an appropriate value - byte[] data = null; // TODO: Initialize to an appropriate value - KeyHostAlgorithm target = new KeyHostAlgorithm(name, key, data); - Assert.Inconclusive("TODO: Implement code to verify target"); - } - - /// - ///A test for Sign - /// - [TestMethod] - [Ignore] // placeholder for actual test - public void SignTest() - { - string name = string.Empty; // TODO: Initialize to an appropriate value - Key key = null; // TODO: Initialize to an appropriate value - KeyHostAlgorithm target = new KeyHostAlgorithm(name, key); // TODO: Initialize to an appropriate value - byte[] data = null; // TODO: Initialize to an appropriate value - byte[] expected = null; // TODO: Initialize to an appropriate value - byte[] actual; - actual = target.Sign(data); - Assert.AreEqual(expected, actual); - Assert.Inconclusive("Verify the correctness of this test method."); - } - - /// - ///A test for VerifySignature - /// - [TestMethod] - [Ignore] // placeholder for actual test - public void VerifySignatureTest() - { - string name = string.Empty; // TODO: Initialize to an appropriate value - Key key = null; // TODO: Initialize to an appropriate value - KeyHostAlgorithm target = new KeyHostAlgorithm(name, key); // TODO: Initialize to an appropriate value - byte[] data = null; // TODO: Initialize to an appropriate value - byte[] signature = null; // TODO: Initialize to an appropriate value - bool expected = false; // TODO: Initialize to an appropriate value - bool actual; - actual = target.VerifySignature(data, signature); - Assert.AreEqual(expected, actual); - Assert.Inconclusive("Verify the correctness of this test method."); - } - - /// - ///A test for Data - /// - [TestMethod] - [Ignore] // placeholder for actual test - public void DataTest() - { - string name = string.Empty; // TODO: Initialize to an appropriate value - Key key = null; // TODO: Initialize to an appropriate value - KeyHostAlgorithm target = new KeyHostAlgorithm(name, key); // TODO: Initialize to an appropriate value - byte[] actual; - actual = target.Data; - Assert.Inconclusive("Verify the correctness of this test method."); - } - } -} \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileTest.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileTest.cs index d1716a957..285d4aa51 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileTest.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileTest.cs @@ -1,10 +1,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Common; + using Renci.SshNet.Sftp; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; + using System; -using System.IO; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -14,121 +13,6 @@ namespace Renci.SshNet.Tests.Classes.Sftp [TestClass] public class SftpFileTest : TestBase { - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - public void Test_Get_Root_Directory() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - var directory = sftp.Get("/"); - - Assert.AreEqual("/", directory.FullName); - Assert.IsTrue(directory.IsDirectory); - Assert.IsFalse(directory.IsRegularFile); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [ExpectedException(typeof(SftpPathNotFoundException))] - public void Test_Get_Invalid_Directory() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - sftp.Get("/xyz"); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - public void Test_Get_File() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - sftp.UploadFile(new MemoryStream(), "abc.txt"); - - var file = sftp.Get("abc.txt"); - - Assert.AreEqual("/home/tester/abc.txt", file.FullName); - Assert.IsTrue(file.IsRegularFile); - Assert.IsFalse(file.IsDirectory); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [Description("Test passing null to Get.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Test_Get_File_Null() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - var file = sftp.Get(null); - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - public void Test_Get_International_File() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - sftp.UploadFile(new MemoryStream(), "test-üöä-"); - - var file = sftp.Get("test-üöä-"); - - Assert.AreEqual("/home/tester/test-üöä-", file.FullName); - Assert.IsTrue(file.IsRegularFile); - Assert.IsFalse(file.IsDirectory); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - public void Test_Sftp_SftpFile_MoveTo() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - string uploadedFileName = Path.GetTempFileName(); - string remoteFileName = Path.GetRandomFileName(); - string newFileName = Path.GetRandomFileName(); - - this.CreateTestFile(uploadedFileName, 1); - - using (var file = File.OpenRead(uploadedFileName)) - { - sftp.UploadFile(file, remoteFileName); - } - - var sftpFile = sftp.Get(remoteFileName); - - sftpFile.MoveTo(newFileName); - - Assert.AreEqual(newFileName, sftpFile.Name); - - sftp.Disconnect(); - } - } - /// ///A test for Delete /// @@ -623,4 +507,4 @@ public void UserIdTest() } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.Connect.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest.Connect.cs index 9ae69f6ec..c2798104a 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.Connect.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest.Connect.cs @@ -20,7 +20,7 @@ public void Connect_HostNameInvalid_ShouldThrowSocketExceptionWithErrorCodeHostN } catch (SocketException ex) { - Assert.AreEqual(ex.ErrorCode, (int) SocketError.HostNotFound); + Assert.IsTrue(ex.ErrorCode is (int) SocketError.HostNotFound or (int) SocketError.TryAgain); } } @@ -38,7 +38,7 @@ public void Connect_ProxyHostNameInvalid_ShouldThrowSocketExceptionWithErrorCode } catch (SocketException ex) { - Assert.AreEqual(ex.ErrorCode, (int)SocketError.HostNotFound); + Assert.IsTrue(ex.ErrorCode is (int) SocketError.HostNotFound or (int) SocketError.TryAgain); } } } diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.CreateDirectory.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest.CreateDirectory.cs deleted file mode 100644 index 73e5f3c83..000000000 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.CreateDirectory.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Renci.SshNet.Common; -using Renci.SshNet.Tests.Properties; -using System; - -namespace Renci.SshNet.Tests.Classes -{ - /// - /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. - /// - public partial class SftpClientTest - { - [TestMethod] - [TestCategory("Sftp")] - [ExpectedException(typeof(SshConnectionException))] - public void Test_Sftp_CreateDirectory_Without_Connecting() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.CreateDirectory("test"); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - public void Test_Sftp_CreateDirectory_In_Current_Location() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - sftp.CreateDirectory("test"); - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [ExpectedException(typeof(SftpPermissionDeniedException))] - public void Test_Sftp_CreateDirectory_In_Forbidden_Directory() - { - if (Resources.USERNAME == "root") - { - Assert.Fail("Must not run this test as root!"); - } - - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - sftp.CreateDirectory("/sbin/test"); - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [ExpectedException(typeof(SftpPathNotFoundException))] - public void Test_Sftp_CreateDirectory_Invalid_Path() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - sftp.CreateDirectory("/abcdefg/abcefg"); - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [ExpectedException(typeof(SshException))] - public void Test_Sftp_CreateDirectory_Already_Exists() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - sftp.CreateDirectory("test"); - - sftp.CreateDirectory("test"); - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [Description("Test passing null to CreateDirectory.")] - [ExpectedException(typeof(ArgumentException))] - public void Test_Sftp_CreateDirectory_Null() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.CreateDirectory(null); - } - } - } -} diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.DeleteDirectory.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest.DeleteDirectory.cs index 0964d4690..d25fb37fd 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.DeleteDirectory.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest.DeleteDirectory.cs @@ -1,7 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Common; using Renci.SshNet.Tests.Properties; -using System; namespace Renci.SshNet.Tests.Classes { @@ -20,75 +19,5 @@ public void Test_Sftp_DeleteDirectory_Without_Connecting() sftp.DeleteDirectory("test"); } } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [ExpectedException(typeof(SftpPathNotFoundException))] - public void Test_Sftp_DeleteDirectory_Which_Doesnt_Exists() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - sftp.DeleteDirectory("abcdef"); - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [ExpectedException(typeof(SftpPermissionDeniedException))] - public void Test_Sftp_DeleteDirectory_Which_No_Permissions() - { - if (Resources.USERNAME == "root") - { - Assert.Fail("Must not run this test as root!"); - } - - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - sftp.DeleteDirectory("/usr"); - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - public void Test_Sftp_DeleteDirectory() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - sftp.CreateDirectory("abcdef"); - sftp.DeleteDirectory("abcdef"); - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [Description("Test passing null to DeleteDirectory.")] - [ExpectedException(typeof(ArgumentException))] - public void Test_Sftp_DeleteDirectory_Null() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - sftp.DeleteDirectory(null); - - sftp.Disconnect(); - } - } } } diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.ListDirectory.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest.ListDirectory.cs index 6a19ce5a3..ceafd4c50 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.ListDirectory.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest.ListDirectory.cs @@ -1,13 +1,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Common; using Renci.SshNet.Tests.Properties; -using System; + using System.Diagnostics; -using System.Linq; -#if NET6_0_OR_GREATER -using System.Threading; -using System.Threading.Tasks; -#endif namespace Renci.SshNet.Tests.Classes { @@ -30,267 +25,5 @@ public void Test_Sftp_ListDirectory_Without_Connecting() } } } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [ExpectedException(typeof(SftpPermissionDeniedException))] - public void Test_Sftp_ListDirectory_Permission_Denied() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - var files = sftp.ListDirectory("/root"); - foreach (var file in files) - { - Debug.WriteLine(file.FullName); - } - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [ExpectedException(typeof(SftpPathNotFoundException))] - public void Test_Sftp_ListDirectory_Not_Exists() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - var files = sftp.ListDirectory("/asdfgh"); - foreach (var file in files) - { - Debug.WriteLine(file.FullName); - } - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - public void Test_Sftp_ListDirectory_Current() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - var files = sftp.ListDirectory("."); - - Assert.IsTrue(files.Count() > 0); - - foreach (var file in files) - { - Debug.WriteLine(file.FullName); - } - - sftp.Disconnect(); - } - } - -#if NET6_0_OR_GREATER - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - public async Task Test_Sftp_ListDirectoryAsync_Current() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - var cts = new CancellationTokenSource(); - cts.CancelAfter(TimeSpan.FromMinutes(1)); - var count = 0; - await foreach(var file in sftp.ListDirectoryAsync(".", cts.Token)) - { - count++; - Debug.WriteLine(file.FullName); - } - - Assert.IsTrue(count > 0); - - sftp.Disconnect(); - } - } -#endif - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - public void Test_Sftp_ListDirectory_Empty() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - var files = sftp.ListDirectory(string.Empty); - - Assert.IsTrue(files.Count() > 0); - - foreach (var file in files) - { - Debug.WriteLine(file.FullName); - } - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [Description("Test passing null to ListDirectory.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Test_Sftp_ListDirectory_Null() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - var files = sftp.ListDirectory(null); - - Assert.IsTrue(files.Count() > 0); - - foreach (var file in files) - { - Debug.WriteLine(file.FullName); - } - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - public void Test_Sftp_ListDirectory_HugeDirectory() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - // Create 10000 directory items - for (int i = 0; i < 10000; i++) - { - sftp.CreateDirectory(string.Format("test_{0}", i)); - Debug.WriteLine("Created " + i); - } - - var files = sftp.ListDirectory("."); - - // Ensure that directory has at least 10000 items - Assert.IsTrue(files.Count() > 10000); - - sftp.Disconnect(); - } - - RemoveAllFiles(); - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - public void Test_Sftp_Change_Directory() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - Assert.AreEqual(sftp.WorkingDirectory, "/home/tester"); - - sftp.CreateDirectory("test1"); - - sftp.ChangeDirectory("test1"); - - Assert.AreEqual(sftp.WorkingDirectory, "/home/tester/test1"); - - sftp.CreateDirectory("test1_1"); - sftp.CreateDirectory("test1_2"); - sftp.CreateDirectory("test1_3"); - - var files = sftp.ListDirectory("."); - - Assert.IsTrue(files.First().FullName.StartsWith(string.Format("{0}", sftp.WorkingDirectory))); - - sftp.ChangeDirectory("test1_1"); - - Assert.AreEqual(sftp.WorkingDirectory, "/home/tester/test1/test1_1"); - - sftp.ChangeDirectory("../test1_2"); - - Assert.AreEqual(sftp.WorkingDirectory, "/home/tester/test1/test1_2"); - - sftp.ChangeDirectory(".."); - - Assert.AreEqual(sftp.WorkingDirectory, "/home/tester/test1"); - - sftp.ChangeDirectory(".."); - - Assert.AreEqual(sftp.WorkingDirectory, "/home/tester"); - - files = sftp.ListDirectory("test1/test1_1"); - - Assert.IsTrue(files.First().FullName.StartsWith(string.Format("{0}/test1/test1_1", sftp.WorkingDirectory))); - - sftp.ChangeDirectory("test1/test1_1"); - - Assert.AreEqual(sftp.WorkingDirectory, "/home/tester/test1/test1_1"); - - sftp.ChangeDirectory("/home/tester/test1/test1_1"); - - Assert.AreEqual(sftp.WorkingDirectory, "/home/tester/test1/test1_1"); - - sftp.ChangeDirectory("/home/tester/test1/test1_1/../test1_2"); - - Assert.AreEqual(sftp.WorkingDirectory, "/home/tester/test1/test1_2"); - - sftp.ChangeDirectory("../../"); - - sftp.DeleteDirectory("test1/test1_1"); - sftp.DeleteDirectory("test1/test1_2"); - sftp.DeleteDirectory("test1/test1_3"); - sftp.DeleteDirectory("test1"); - - sftp.Disconnect(); - } - - RemoveAllFiles(); - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [Description("Test passing null to ChangeDirectory.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Test_Sftp_ChangeDirectory_Null() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - - sftp.ChangeDirectory(null); - - sftp.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Sftp")] - [TestCategory("integration")] - [Description("Test calling EndListDirectory method more then once.")] - [ExpectedException(typeof(ArgumentException))] - public void Test_Sftp_Call_EndListDirectory_Twice() - { - using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - sftp.Connect(); - var ar = sftp.BeginListDirectory("/", null, null); - var result = sftp.EndListDirectory(ar); - var result1 = sftp.EndListDirectory(ar); - } - } } } diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest.cs index 200abae89..9e1c1f3c7 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest.cs @@ -1,11 +1,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Sftp; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; using System; using System.Collections.Generic; using System.IO; -using System.Security.Cryptography; using System.Text; namespace Renci.SshNet.Tests.Classes @@ -1355,69 +1353,5 @@ public void WorkingDirectoryTest() actual = target.WorkingDirectory; Assert.Inconclusive("Verify the correctness of this test method."); } - - protected static string CalculateMD5(string fileName) - { - using (FileStream file = new FileStream(fileName, FileMode.Open)) - { -#if NET7_0_OR_GREATER - var hash = MD5.HashData(file); -#else -#if NET6_0 - var md5 = MD5.Create(); -#else - MD5 md5 = new MD5CryptoServiceProvider(); -#endif // NET6_0 - var hash = md5.ComputeHash(file); -#endif // NET7_0_OR_GREATER - - file.Close(); - - StringBuilder sb = new StringBuilder(); - for (var i = 0; i < hash.Length; i++) - { - sb.Append(hash[i].ToString("x2")); - } - return sb.ToString(); - } - } - - private static void RemoveAllFiles() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - client.RunCommand("rm -rf *"); - client.Disconnect(); - } - } - - /// - /// Helper class to help with upload and download testing - /// - private class TestInfo - { - public string RemoteFileName { get; set; } - - public string UploadedFileName { get; set; } - - public string DownloadedFileName { get; set; } - - //public ulong UploadedBytes { get; set; } - - //public ulong DownloadedBytes { get; set; } - - public FileStream UploadedFile { get; set; } - - public FileStream DownloadedFile { get; set; } - - public string UploadedHash { get; set; } - - public string DownloadedHash { get; set; } - - public SftpUploadAsyncResult UploadResult { get; set; } - - public SftpDownloadAsyncResult DownloadResult { get; set; } - } } } diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest.cs index 1eabcae20..8ee74c2ca 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest.cs @@ -2,11 +2,10 @@ using Renci.SshNet.Common; using Renci.SshNet.Tests.Common; using Renci.SshNet.Tests.Properties; -using System; + using System.Collections.Generic; using System.IO; using System.Text; -using System.Linq; namespace Renci.SshNet.Tests.Classes { @@ -16,249 +15,6 @@ namespace Renci.SshNet.Tests.Classes [TestClass] public class SshClientTest : TestBase { - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - public void Test_Connect_Using_Correct_Password() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - - #region Example SshClient(host, username) Connect - using (var client = new SshClient(host, username, password)) - { - client.Connect(); - // Do something here - client.Disconnect(); - } - #endregion - } - - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - public void Test_Connect_Handle_HostKeyReceived() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - var hostKeyValidated = false; - - #region Example SshClient Connect HostKeyReceived - using (var client = new SshClient(host, username, password)) - { - client.HostKeyReceived += delegate(object sender, HostKeyEventArgs e) - { - hostKeyValidated = true; - - if (e.FingerPrint.SequenceEqual(new byte[] { 0x00, 0x01, 0x02, 0x03 })) - { - e.CanTrust = true; - } - else - { - e.CanTrust = false; - } - }; - client.Connect(); - // Do something here - client.Disconnect(); - } - #endregion - - Assert.IsTrue(hostKeyValidated); - } - - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - public void Test_Connect_Timeout() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - - #region Example SshClient Connect Timeout - - var connectionInfo = new PasswordConnectionInfo(host, username, password) - { - Timeout = TimeSpan.FromSeconds(30) - }; - - using (var client = new SshClient(connectionInfo)) - { - client.Connect(); - // Do something here - client.Disconnect(); - } - #endregion - Assert.Inconclusive(); - } - - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - public void Test_Connect_Handle_ErrorOccurred() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - var exceptionOccured = false; - - #region Example SshClient Connect ErrorOccurred - using (var client = new SshClient(host, username, password)) - { - client.ErrorOccurred += delegate(object sender, ExceptionEventArgs e) - { - Console.WriteLine("Error occured: " + e.Exception); - exceptionOccured = true; - }; - - client.Connect(); - // Do something here - client.Disconnect(); - } - #endregion - Assert.IsTrue(exceptionOccured); - } - - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - [ExpectedException(typeof(SshAuthenticationException))] - public void Test_Connect_Using_Invalid_Password() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, "invalid password")) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - public void Test_Connect_Using_Rsa_Key_Without_PassPhrase() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - MemoryStream keyFileStream = new MemoryStream(Encoding.ASCII.GetBytes(Resources.RSA_KEY_WITHOUT_PASS)); - - #region Example SshClient(host, username) Connect PrivateKeyFile - using (var client = new SshClient(host, username, new PrivateKeyFile(keyFileStream))) - { - client.Connect(); - client.Disconnect(); - } - #endregion - } - - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - public void Test_Connect_Using_RsaKey_With_PassPhrase() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var passphrase = Resources.PASSWORD; - MemoryStream keyFileStream = new MemoryStream(Encoding.ASCII.GetBytes(Resources.RSA_KEY_WITH_PASS)); - - #region Example SshClient(host, username) Connect PrivateKeyFile PassPhrase - using (var client = new SshClient(host, username, new PrivateKeyFile(keyFileStream, passphrase))) - { - client.Connect(); - client.Disconnect(); - } - #endregion - } - - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - [ExpectedException(typeof(SshPassPhraseNullOrEmptyException))] - public void Test_Connect_Using_Key_With_Empty_PassPhrase() - { - MemoryStream keyFileStream = new MemoryStream(Encoding.ASCII.GetBytes(Resources.RSA_KEY_WITH_PASS)); - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, new PrivateKeyFile(keyFileStream, null))) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - public void Test_Connect_Using_DsaKey_Without_PassPhrase() - { - MemoryStream keyFileStream = new MemoryStream(Encoding.ASCII.GetBytes(Resources.DSA_KEY_WITHOUT_PASS)); - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, new PrivateKeyFile(keyFileStream))) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - public void Test_Connect_Using_DsaKey_With_PassPhrase() - { - MemoryStream keyFileStream = new MemoryStream(Encoding.ASCII.GetBytes(Resources.DSA_KEY_WITH_PASS)); - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, new PrivateKeyFile(keyFileStream, Resources.PASSWORD))) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - [ExpectedException(typeof(SshAuthenticationException))] - public void Test_Connect_Using_Invalid_PrivateKey() - { - MemoryStream keyFileStream = new MemoryStream(Encoding.ASCII.GetBytes(Resources.INVALID_KEY)); - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, new PrivateKeyFile(keyFileStream))) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - public void Test_Connect_Using_Multiple_PrivateKeys() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, - new PrivateKeyFile(new MemoryStream(Encoding.ASCII.GetBytes(Resources.INVALID_KEY))), - new PrivateKeyFile(new MemoryStream(Encoding.ASCII.GetBytes(Resources.DSA_KEY_WITH_PASS)), Resources.PASSWORD), - new PrivateKeyFile(new MemoryStream(Encoding.ASCII.GetBytes(Resources.RSA_KEY_WITH_PASS)), Resources.PASSWORD), - new PrivateKeyFile(new MemoryStream(Encoding.ASCII.GetBytes(Resources.RSA_KEY_WITHOUT_PASS))), - new PrivateKeyFile(new MemoryStream(Encoding.ASCII.GetBytes(Resources.DSA_KEY_WITHOUT_PASS))) - )) - { - client.Connect(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("Authentication")] - [TestCategory("integration")] - public void Test_Connect_Then_Reconnect() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - client.Disconnect(); - client.Connect(); - client.Disconnect(); - } - } - [TestMethod] public void CreateShellStream1_NeverConnected() { diff --git a/src/Renci.SshNet.Tests/Classes/SshCommandTest.cs b/src/Renci.SshNet.Tests/Classes/SshCommandTest.cs index d43a11e5e..3f5450952 100644 --- a/src/Renci.SshNet.Tests/Classes/SshCommandTest.cs +++ b/src/Renci.SshNet.Tests/Classes/SshCommandTest.cs @@ -3,13 +3,7 @@ using Renci.SshNet.Tests.Common; using Renci.SshNet.Tests.Properties; using System; -using System.IO; using System.Text; -using System.Threading; -#if FEATURE_TPL -using System.Diagnostics; -using System.Threading.Tasks; -#endif // FEATURE_TPL namespace Renci.SshNet.Tests.Classes { @@ -31,480 +25,6 @@ public void Test_Execute_SingleCommand_Without_Connecting() } } - [TestMethod] - [TestCategory("integration")] - public void Test_Run_SingleCommand() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - - using (var client = new SshClient(host, username, password)) - { - #region Example SshCommand RunCommand Result - client.Connect(); - - var testValue = Guid.NewGuid().ToString(); - var command = client.RunCommand(string.Format("echo {0}", testValue)); - var result = command.Result; - result = result.Substring(0, result.Length - 1); // Remove \n character returned by command - - client.Disconnect(); - #endregion - - Assert.IsTrue(result.Equals(testValue)); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_SingleCommand() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - - using (var client = new SshClient(host, username, password)) - { - #region Example SshCommand CreateCommand Execute - client.Connect(); - - var testValue = Guid.NewGuid().ToString(); - var command = string.Format("echo {0}", testValue); - var cmd = client.CreateCommand(command); - var result = cmd.Execute(); - result = result.Substring(0, result.Length - 1); // Remove \n character returned by command - - client.Disconnect(); - #endregion - - Assert.IsTrue(result.Equals(testValue)); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_OutputStream() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - - using (var client = new SshClient(host, username, password)) - { - #region Example SshCommand CreateCommand Execute OutputStream - client.Connect(); - - var cmd = client.CreateCommand("ls -l"); // very long list - var asynch = cmd.BeginExecute(); - - var reader = new StreamReader(cmd.OutputStream); - - while (!asynch.IsCompleted) - { - var result = reader.ReadToEnd(); - if (string.IsNullOrEmpty(result)) - { - continue; - } - - Console.Write(result); - } - - _ = cmd.EndExecute(asynch); - - client.Disconnect(); - #endregion - - Assert.Inconclusive(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_ExtendedOutputStream() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - - using (var client = new SshClient(host, username, password)) - { - #region Example SshCommand CreateCommand Execute ExtendedOutputStream - - client.Connect(); - var cmd = client.CreateCommand("echo 12345; echo 654321 >&2"); - var result = cmd.Execute(); - - Console.Write(result); - - var reader = new StreamReader(cmd.ExtendedOutputStream); - Console.WriteLine("DEBUG:"); - Console.Write(reader.ReadToEnd()); - - client.Disconnect(); - - #endregion - - Assert.Inconclusive(); - } - } - - [TestMethod] - [TestCategory("integration")] - [ExpectedException(typeof(SshOperationTimeoutException))] - public void Test_Execute_Timeout() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - #region Example SshCommand CreateCommand Execute CommandTimeout - client.Connect(); - var cmd = client.CreateCommand("sleep 10s"); - cmd.CommandTimeout = TimeSpan.FromSeconds(5); - cmd.Execute(); - client.Disconnect(); - #endregion - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_Infinite_Timeout() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - var cmd = client.CreateCommand("sleep 10s"); - cmd.Execute(); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_InvalidCommand() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - - var cmd = client.CreateCommand(";"); - cmd.Execute(); - if (string.IsNullOrEmpty(cmd.Error)) - { - Assert.Fail("Operation should fail"); - } - Assert.IsTrue(cmd.ExitStatus > 0); - - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_InvalidCommand_Then_Execute_ValidCommand() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - var cmd = client.CreateCommand(";"); - cmd.Execute(); - if (string.IsNullOrEmpty(cmd.Error)) - { - Assert.Fail("Operation should fail"); - } - Assert.IsTrue(cmd.ExitStatus > 0); - - var result = ExecuteTestCommand(client); - - client.Disconnect(); - - Assert.IsTrue(result); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_Command_with_ExtendedOutput() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - var cmd = client.CreateCommand("echo 12345; echo 654321 >&2"); - cmd.Execute(); - - //var extendedData = Encoding.ASCII.GetString(cmd.ExtendedOutputStream.ToArray()); - var extendedData = new StreamReader(cmd.ExtendedOutputStream, Encoding.ASCII).ReadToEnd(); - client.Disconnect(); - - Assert.AreEqual("12345\n", cmd.Result); - Assert.AreEqual("654321\n", extendedData); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_Command_Reconnect_Execute_Command() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - var result = ExecuteTestCommand(client); - Assert.IsTrue(result); - - client.Disconnect(); - client.Connect(); - result = ExecuteTestCommand(client); - Assert.IsTrue(result); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_Command_ExitStatus() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - #region Example SshCommand RunCommand ExitStatus - client.Connect(); - - var cmd = client.RunCommand("exit 128"); - - Console.WriteLine(cmd.ExitStatus); - - client.Disconnect(); - #endregion - - Assert.IsTrue(cmd.ExitStatus == 128); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_Command_Asynchronously() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - - var cmd = client.CreateCommand("sleep 5s; echo 'test'"); - var asyncResult = cmd.BeginExecute(null, null); - while (!asyncResult.IsCompleted) - { - Thread.Sleep(100); - } - - cmd.EndExecute(asyncResult); - - Assert.IsTrue(cmd.Result == "test\n"); - - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_Command_Asynchronously_With_Error() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - - var cmd = client.CreateCommand("sleep 5s; ;"); - var asyncResult = cmd.BeginExecute(null, null); - while (!asyncResult.IsCompleted) - { - Thread.Sleep(100); - } - - cmd.EndExecute(asyncResult); - - Assert.IsFalse(string.IsNullOrEmpty(cmd.Error)); - - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_Command_Asynchronously_With_Callback() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - - var callbackCalled = false; - - var cmd = client.CreateCommand("sleep 5s; echo 'test'"); - var asyncResult = cmd.BeginExecute(new AsyncCallback((s) => - { - callbackCalled = true; - }), null); - while (!asyncResult.IsCompleted) - { - Thread.Sleep(100); - } - - cmd.EndExecute(asyncResult); - - Assert.IsTrue(callbackCalled); - - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_Command_Asynchronously_With_Callback_On_Different_Thread() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - - var currentThreadId = Thread.CurrentThread.ManagedThreadId; - int callbackThreadId = 0; - - var cmd = client.CreateCommand("sleep 5s; echo 'test'"); - var asyncResult = cmd.BeginExecute(new AsyncCallback((s) => - { - callbackThreadId = Thread.CurrentThread.ManagedThreadId; - }), null); - while (!asyncResult.IsCompleted) - { - Thread.Sleep(100); - } - - cmd.EndExecute(asyncResult); - - Assert.AreNotEqual(currentThreadId, callbackThreadId); - - client.Disconnect(); - } - } - - /// - /// Tests for Issue 563. - /// - [WorkItem(563), TestMethod] - [TestCategory("integration")] - public void Test_Execute_Command_Same_Object_Different_Commands() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - var cmd = client.CreateCommand("echo 12345"); - cmd.Execute(); - Assert.AreEqual("12345\n", cmd.Result); - cmd.Execute("echo 23456"); - Assert.AreEqual("23456\n", cmd.Result); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Get_Result_Without_Execution() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - var cmd = client.CreateCommand("ls -l"); - - Assert.IsTrue(string.IsNullOrEmpty(cmd.Result)); - client.Disconnect(); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Get_Error_Without_Execution() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - var cmd = client.CreateCommand("ls -l"); - - Assert.IsTrue(string.IsNullOrEmpty(cmd.Error)); - client.Disconnect(); - } - } - - [WorkItem(703), TestMethod] - [ExpectedException(typeof(ArgumentException))] - [TestCategory("integration")] - public void Test_EndExecute_Before_BeginExecute() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - var cmd = client.CreateCommand("ls -l"); - cmd.EndExecute(null); - client.Disconnect(); - } - } - - /// - ///A test for BeginExecute - /// - [TestMethod()] - [TestCategory("integration")] - public void BeginExecuteTest() - { - string expected = "123\n"; - string result; - - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - #region Example SshCommand CreateCommand BeginExecute IsCompleted EndExecute - - client.Connect(); - - var cmd = client.CreateCommand("sleep 15s;echo 123"); // Perform long running task - - var asynch = cmd.BeginExecute(); - - while (!asynch.IsCompleted) - { - // Waiting for command to complete... - Thread.Sleep(2000); - } - result = cmd.EndExecute(asynch); - client.Disconnect(); - - #endregion - - Assert.IsNotNull(asynch); - Assert.AreEqual(expected, result); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_Execute_Invalid_Command() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - #region Example SshCommand CreateCommand Error - - client.Connect(); - - var cmd = client.CreateCommand(";"); - cmd.Execute(); - if (!string.IsNullOrEmpty(cmd.Error)) - { - Console.WriteLine(cmd.Error); - } - - client.Disconnect(); - - #endregion - - Assert.Inconclusive(); - } - } - - /// ///A test for BeginExecute /// @@ -630,100 +150,6 @@ public void ResultTest() Assert.Inconclusive("Verify the correctness of this test method."); } -#if FEATURE_TPL - [TestMethod] - [TestCategory("integration")] - public void Test_MultipleThread_Example_MultipleConnections() - { - var host = Resources.HOST; - var username = Resources.USERNAME; - var password = Resources.PASSWORD; - - try - { -#region Example SshCommand RunCommand Parallel - System.Threading.Tasks.Parallel.For(0, 10000, - () => - { - var client = new SshClient(host, username, password); - client.Connect(); - return client; - }, - (int counter, ParallelLoopState pls, SshClient client) => - { - var result = client.RunCommand("echo 123"); - Debug.WriteLine(string.Format("TestMultipleThreadMultipleConnections #{0}", counter)); - return client; - }, - (SshClient client) => - { - client.Disconnect(); - client.Dispose(); - } - ); -#endregion - - } - catch (Exception exp) - { - Assert.Fail(exp.ToString()); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_MultipleThread_10000_MultipleConnections() - { - try - { - System.Threading.Tasks.Parallel.For(0, 10000, - () => - { - var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD); - client.Connect(); - return client; - }, - (int counter, ParallelLoopState pls, SshClient client) => - { - var result = ExecuteTestCommand(client); - Debug.WriteLine(string.Format("TestMultipleThreadMultipleConnections #{0}", counter)); - Assert.IsTrue(result); - return client; - }, - (SshClient client) => - { - client.Disconnect(); - client.Dispose(); - } - ); - } - catch (Exception exp) - { - Assert.Fail(exp.ToString()); - } - } - - [TestMethod] - [TestCategory("integration")] - public void Test_MultipleThread_10000_MultipleSessions() - { - using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD)) - { - client.Connect(); - System.Threading.Tasks.Parallel.For(0, 10000, - (counter) => - { - var result = ExecuteTestCommand(client); - Debug.WriteLine(string.Format("TestMultipleThreadMultipleConnections #{0}", counter)); - Assert.IsTrue(result); - } - ); - - client.Disconnect(); - } - } -#endif // FEATURE_TPL - private static bool ExecuteTestCommand(SshClient s) { var testValue = Guid.NewGuid().ToString(); diff --git a/src/Renci.SshNet.sln b/src/Renci.SshNet.sln index be2686bee..34cc0f483 100644 --- a/src/Renci.SshNet.sln +++ b/src/Renci.SshNet.sln @@ -42,7 +42,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D21A4D03-0 ..\test\Directory.Build.props = ..\test\Directory.Build.props EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTests", "Renci.SshNet.IntegrationTests\IntegrationTests.csproj", "{EEF98046-729C-419E-932D-4E569073C8CC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Renci.SshNet.IntegrationTests", "Renci.SshNet.IntegrationTests\Renci.SshNet.IntegrationTests.csproj", "{EEF98046-729C-419E-932D-4E569073C8CC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Renci.SshNet.TestTools.OpenSSH", "Renci.SshNet.TestTools.OpenSSH\Renci.SshNet.TestTools.OpenSSH.csproj", "{78239046-2019-494E-B6EC-240AF787E4D0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -106,6 +108,26 @@ Global {EEF98046-729C-419E-932D-4E569073C8CC}.Release|x64.Build.0 = Release|Any CPU {EEF98046-729C-419E-932D-4E569073C8CC}.Release|x86.ActiveCfg = Release|Any CPU {EEF98046-729C-419E-932D-4E569073C8CC}.Release|x86.Build.0 = Release|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Debug|ARM.ActiveCfg = Debug|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Debug|ARM.Build.0 = Debug|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Debug|x64.ActiveCfg = Debug|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Debug|x64.Build.0 = Debug|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Debug|x86.ActiveCfg = Debug|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Debug|x86.Build.0 = Debug|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Release|Any CPU.Build.0 = Release|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Release|ARM.ActiveCfg = Release|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Release|ARM.Build.0 = Release|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Release|x64.ActiveCfg = Release|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Release|x64.Build.0 = Release|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Release|x86.ActiveCfg = Release|Any CPU + {78239046-2019-494E-B6EC-240AF787E4D0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Renci.SshNet/Properties/AssemblyInfo.cs b/src/Renci.SshNet/Properties/AssemblyInfo.cs index cde7dcdeb..07f66e5fe 100644 --- a/src/Renci.SshNet/Properties/AssemblyInfo.cs +++ b/src/Renci.SshNet/Properties/AssemblyInfo.cs @@ -5,4 +5,5 @@ [assembly: AssemblyTitle("SSH.NET")] [assembly: Guid("ad816c5e-6f13-4589-9f3e-59523f8b77a4")] [assembly: InternalsVisibleTo("Renci.SshNet.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f9194e1eb66b7e2575aaee115ee1d27bc100920e7150e43992d6f668f9737de8b9c7ae892b62b8a36dd1d57929ff1541665d101dc476d6e02390846efae7e5186eec409710fdb596e3f83740afef0d4443055937649bc5a773175b61c57615dac0f0fd10f52b52fedf76c17474cc567b3f7a79de95dde842509fb39aaf69c6c2")] +[assembly: InternalsVisibleTo("Renci.SshNet.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f9194e1eb66b7e2575aaee115ee1d27bc100920e7150e43992d6f668f9737de8b9c7ae892b62b8a36dd1d57929ff1541665d101dc476d6e02390846efae7e5186eec409710fdb596e3f83740afef0d4443055937649bc5a773175b61c57615dac0f0fd10f52b52fedf76c17474cc567b3f7a79de95dde842509fb39aaf69c6c2")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]