mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-10 09:15:47 +00:00
89f378a0ab
* Tweak internal IProgress usage for APM and sync UploadFile/DownloadFile Changes to support IProgress<> callback on UploadAsync/DownloadAsync meant wrapping the Action<> callback on existing methods in a Progress<>, which posts the callback onto the current synchronisation context rather than the threadpool. For the legacy APM methods (Begin[..]), let's just preserve their old behaviour. For the synchronous methods, posting to the synchronisation context is probably the worst choice (because if there is one, the method itself is running there). We can either revert to the threadpool as well, or take the opportunity to invoke the callback synchronously, which is a behavioural change but probably the least surprising behaviour for a synchronous method. * keep callbacks on the threadpool Actually, we could call the Download callback synchronously easily enough, but the Upload progress reports are being made on the message listener thread upon request ack. A more involved scheme could drain callbacks to fire during the read loop. For now just make it all the same behaviour as in 2025.1.0.
527 lines
20 KiB
C#
527 lines
20 KiB
C#
using Renci.SshNet.Common;
|
|
using Renci.SshNet.Sftp;
|
|
|
|
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
|
|
{
|
|
/// <summary>
|
|
/// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
|
|
/// </summary>
|
|
public partial class SftpClientTest : IntegrationTestBase
|
|
{
|
|
[TestMethod]
|
|
[TestCategory("Sftp")]
|
|
public void Test_Sftp_Upload_And_Download_1MB_File()
|
|
{
|
|
RemoveAllFiles();
|
|
|
|
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
|
|
{
|
|
sftp.Connect();
|
|
|
|
var uploadedFileName = Path.GetTempFileName();
|
|
var remoteFileName = Path.GetRandomFileName();
|
|
|
|
CreateTestFile(uploadedFileName, 1);
|
|
|
|
// Calculate has value
|
|
var uploadedHash = CalculateMD5(uploadedFileName);
|
|
|
|
using (var file = File.OpenRead(uploadedFileName))
|
|
{
|
|
sftp.UploadFile(file, remoteFileName);
|
|
}
|
|
|
|
var downloadedFileName = Path.GetTempFileName();
|
|
|
|
using (var file = File.OpenWrite(downloadedFileName))
|
|
{
|
|
sftp.DownloadFile(remoteFileName, file);
|
|
}
|
|
|
|
var downloadedHash = CalculateMD5(downloadedFileName);
|
|
|
|
sftp.DeleteFile(remoteFileName);
|
|
|
|
File.Delete(uploadedFileName);
|
|
File.Delete(downloadedFileName);
|
|
|
|
sftp.Disconnect();
|
|
|
|
Assert.AreEqual(uploadedHash, downloadedHash);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
[TestCategory("Sftp")]
|
|
public async Task Test_Sftp_Upload_And_Download_Async_1MB_File()
|
|
{
|
|
RemoveAllFiles();
|
|
|
|
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
|
|
{
|
|
await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
|
|
|
|
var uploadedFileName = Path.GetTempFileName();
|
|
var remoteFileName = Path.GetRandomFileName();
|
|
|
|
CreateTestFile(uploadedFileName, 1);
|
|
|
|
// Calculate has value
|
|
var uploadedHash = CalculateMD5(uploadedFileName);
|
|
|
|
using (var file = File.OpenRead(uploadedFileName))
|
|
{
|
|
await sftp.UploadFileAsync(file, remoteFileName).ConfigureAwait(false);
|
|
}
|
|
|
|
// uploading again should not throw because of the default canOverride = true
|
|
using (var file = File.OpenRead(uploadedFileName))
|
|
{
|
|
await sftp.UploadFileAsync(file, remoteFileName).ConfigureAwait(false);
|
|
}
|
|
|
|
// uploading with canOverride = false should throw because the file already exists
|
|
using (var file = File.OpenRead(uploadedFileName))
|
|
{
|
|
await Assert.ThrowsAsync<SftpException>(async () => await sftp.UploadFileAsync(file, remoteFileName, canOverride: false).ConfigureAwait(false));
|
|
}
|
|
|
|
var downloadedFileName = Path.GetTempFileName();
|
|
|
|
using (var file = File.OpenWrite(downloadedFileName))
|
|
{
|
|
await sftp.DownloadFileAsync(remoteFileName, file).ConfigureAwait(false);
|
|
}
|
|
|
|
var downloadedHash = CalculateMD5(downloadedFileName);
|
|
|
|
await sftp.DeleteFileAsync(remoteFileName, CancellationToken.None).ConfigureAwait(false);
|
|
|
|
File.Delete(uploadedFileName);
|
|
File.Delete(downloadedFileName);
|
|
|
|
sftp.Disconnect();
|
|
|
|
Assert.AreEqual(uploadedHash, downloadedHash);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
[TestCategory("Sftp")]
|
|
public void Test_Sftp_Upload_Forbidden()
|
|
{
|
|
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
|
|
{
|
|
sftp.Connect();
|
|
|
|
var uploadedFileName = Path.GetTempFileName();
|
|
var remoteFileName = "/root/1";
|
|
|
|
CreateTestFile(uploadedFileName, 1);
|
|
|
|
using (var file = File.OpenRead(uploadedFileName))
|
|
{
|
|
Assert.ThrowsExactly<SftpPermissionDeniedException>(() => sftp.UploadFile(file, remoteFileName));
|
|
}
|
|
|
|
sftp.Disconnect();
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
[TestCategory("Sftp")]
|
|
public async Task Test_Sftp_UploadAsync_Cancellation_Requested()
|
|
{
|
|
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
|
|
{
|
|
await sftp.ConnectAsync(CancellationToken.None);
|
|
|
|
var uploadedFileName = Path.GetTempFileName();
|
|
var remoteFileName = "/root/1";
|
|
|
|
CreateTestFile(uploadedFileName, 1);
|
|
|
|
var cancelledToken = new CancellationToken(true);
|
|
|
|
using (var file = File.OpenRead(uploadedFileName))
|
|
{
|
|
await Assert.ThrowsAsync<OperationCanceledException>(() => sftp.UploadFileAsync(file, remoteFileName, cancelledToken));
|
|
}
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
[TestCategory("Sftp")]
|
|
public void Test_Sftp_Multiple_Async_Upload_And_Download_10Files_5MB_Each()
|
|
{
|
|
var maxFiles = 10;
|
|
var maxSize = 5;
|
|
|
|
RemoveAllFiles();
|
|
|
|
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
|
|
{
|
|
sftp.OperationTimeout = TimeSpan.FromMinutes(1);
|
|
sftp.Connect();
|
|
|
|
var testInfoList = new Dictionary<string, TestInfo>();
|
|
|
|
for (var i = 0; i < maxFiles; i++)
|
|
{
|
|
var testInfo = new TestInfo
|
|
{
|
|
UploadedFileName = Path.GetTempFileName(),
|
|
DownloadedFileName = Path.GetTempFileName(),
|
|
RemoteFileName = Path.GetRandomFileName()
|
|
};
|
|
|
|
CreateTestFile(testInfo.UploadedFileName, maxSize);
|
|
|
|
// Calculate hash value
|
|
testInfo.UploadedHash = CalculateMD5(testInfo.UploadedFileName);
|
|
|
|
testInfoList.Add(testInfo.RemoteFileName, testInfo);
|
|
}
|
|
|
|
var uploadWaitHandles = new List<WaitHandle>();
|
|
|
|
// Start file uploads
|
|
foreach (var remoteFile in testInfoList.Keys)
|
|
{
|
|
var testInfo = testInfoList[remoteFile];
|
|
testInfo.UploadedFile = File.OpenRead(testInfo.UploadedFileName);
|
|
|
|
testInfo.UploadResult = sftp.BeginUploadFile(testInfo.UploadedFile,
|
|
remoteFile,
|
|
null,
|
|
null) as SftpUploadAsyncResult;
|
|
|
|
uploadWaitHandles.Add(testInfo.UploadResult.AsyncWaitHandle);
|
|
}
|
|
|
|
// Wait for upload to finish
|
|
var uploadCompleted = false;
|
|
while (!uploadCompleted)
|
|
{
|
|
// Assume upload completed
|
|
uploadCompleted = true;
|
|
|
|
foreach (var testInfo in testInfoList.Values)
|
|
{
|
|
var sftpResult = testInfo.UploadResult;
|
|
|
|
if (!testInfo.UploadResult.IsCompleted)
|
|
{
|
|
uploadCompleted = false;
|
|
}
|
|
}
|
|
Thread.Sleep(500);
|
|
}
|
|
|
|
// End file uploads
|
|
foreach (var remoteFile in testInfoList.Keys)
|
|
{
|
|
var testInfo = testInfoList[remoteFile];
|
|
|
|
sftp.EndUploadFile(testInfo.UploadResult);
|
|
testInfo.UploadedFile.Dispose();
|
|
}
|
|
|
|
// Start file downloads
|
|
|
|
var downloadWaitHandles = new List<WaitHandle>();
|
|
|
|
foreach (var remoteFile in testInfoList.Keys)
|
|
{
|
|
var testInfo = testInfoList[remoteFile];
|
|
testInfo.DownloadedFile = File.OpenWrite(testInfo.DownloadedFileName);
|
|
testInfo.DownloadResult = sftp.BeginDownloadFile(remoteFile,
|
|
testInfo.DownloadedFile,
|
|
null,
|
|
null) as SftpDownloadAsyncResult;
|
|
|
|
downloadWaitHandles.Add(testInfo.DownloadResult.AsyncWaitHandle);
|
|
}
|
|
|
|
// Wait for download to finish
|
|
var downloadCompleted = false;
|
|
while (!downloadCompleted)
|
|
{
|
|
// Assume download completed
|
|
downloadCompleted = true;
|
|
|
|
foreach (var testInfo in testInfoList.Values)
|
|
{
|
|
var sftpResult = testInfo.DownloadResult;
|
|
|
|
if (!testInfo.DownloadResult.IsCompleted)
|
|
{
|
|
downloadCompleted = false;
|
|
}
|
|
}
|
|
Thread.Sleep(500);
|
|
}
|
|
|
|
var hashMatches = true;
|
|
var uploadDownloadSizeOk = true;
|
|
|
|
// End file downloads
|
|
foreach (var remoteFile in testInfoList.Keys)
|
|
{
|
|
var testInfo = testInfoList[remoteFile];
|
|
|
|
sftp.EndDownloadFile(testInfo.DownloadResult);
|
|
|
|
testInfo.DownloadedFile.Dispose();
|
|
|
|
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;
|
|
}
|
|
|
|
if (!testInfo.DownloadedHash.Equals(testInfo.UploadedHash))
|
|
{
|
|
hashMatches = false;
|
|
}
|
|
}
|
|
|
|
// Clean up after test
|
|
foreach (var remoteFile in testInfoList.Keys)
|
|
{
|
|
var testInfo = testInfoList[remoteFile];
|
|
|
|
sftp.DeleteFile(remoteFile);
|
|
|
|
File.Delete(testInfo.UploadedFileName);
|
|
File.Delete(testInfo.DownloadedFileName);
|
|
}
|
|
|
|
sftp.Disconnect();
|
|
|
|
Assert.IsTrue(hashMatches, "Hash does not match");
|
|
Assert.IsTrue(uploadDownloadSizeOk, "Uploaded and downloaded bytes does not match");
|
|
}
|
|
}
|
|
|
|
// TODO: Split this test into multiple tests
|
|
[TestMethod]
|
|
[TestCategory("Sftp")]
|
|
[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(SshServerHostName, SshServerPort, User.UserName, User.Password))
|
|
{
|
|
sftp.Connect();
|
|
|
|
var remoteFileName = Path.GetRandomFileName();
|
|
var localFileName = Path.GetRandomFileName();
|
|
using var uploadDelegateEvent = new ManualResetEventSlim();
|
|
using var downloadDelegateEvent = new ManualResetEventSlim();
|
|
using var listDirectoryDelegateEvent = new ManualResetEventSlim();
|
|
using var uploadCallbackEvent = new ManualResetEventSlim();
|
|
using var downloadCallbackEvent = new ManualResetEventSlim();
|
|
using var listDirectoryCallbackEvent = new ManualResetEventSlim();
|
|
IAsyncResult asyncResult;
|
|
|
|
// Test for BeginUploadFile.
|
|
|
|
CreateTestFile(localFileName, 1);
|
|
|
|
var originalContext = SynchronizationContext.Current;
|
|
try
|
|
{
|
|
// Set a throwing context to verify it's not captured by the callback
|
|
SynchronizationContext.SetSynchronizationContext(new ThrowingSynchronizationContext());
|
|
|
|
using (var fileStream = File.OpenRead(localFileName))
|
|
{
|
|
asyncResult = sftp.BeginUploadFile(fileStream,
|
|
remoteFileName,
|
|
delegate (IAsyncResult ar)
|
|
{
|
|
uploadDelegateEvent.Set();
|
|
},
|
|
state: null,
|
|
uploadCallback: _ => uploadCallbackEvent.Set());
|
|
|
|
sftp.EndUploadFile(asyncResult);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
SynchronizationContext.SetSynchronizationContext(originalContext);
|
|
}
|
|
|
|
File.Delete(localFileName);
|
|
|
|
Assert.IsTrue(uploadDelegateEvent.Wait(1000));
|
|
Assert.IsTrue(uploadCallbackEvent.Wait(1000));
|
|
|
|
// Test for BeginDownloadFile.
|
|
|
|
asyncResult = null;
|
|
try
|
|
{
|
|
// Set a throwing context to verify it's not captured by the callback
|
|
SynchronizationContext.SetSynchronizationContext(new ThrowingSynchronizationContext());
|
|
|
|
using (var fileStream = File.OpenWrite(localFileName))
|
|
{
|
|
asyncResult = sftp.BeginDownloadFile(remoteFileName,
|
|
fileStream,
|
|
delegate (IAsyncResult ar)
|
|
{
|
|
downloadDelegateEvent.Set();
|
|
},
|
|
state: null,
|
|
downloadCallback: _ => downloadCallbackEvent.Set());
|
|
|
|
sftp.EndDownloadFile(asyncResult);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
SynchronizationContext.SetSynchronizationContext(originalContext);
|
|
}
|
|
|
|
File.Delete(localFileName);
|
|
|
|
Assert.IsTrue(downloadDelegateEvent.Wait(1000));
|
|
Assert.IsTrue(downloadCallbackEvent.Wait(1000));
|
|
|
|
// Test for BeginListDirectory.
|
|
|
|
try
|
|
{
|
|
// Set a throwing context to verify it's not captured by the callback
|
|
SynchronizationContext.SetSynchronizationContext(new ThrowingSynchronizationContext());
|
|
|
|
asyncResult = sftp.BeginListDirectory(sftp.WorkingDirectory,
|
|
delegate (IAsyncResult ar)
|
|
{
|
|
listDirectoryDelegateEvent.Set();
|
|
},
|
|
state: null,
|
|
listCallback: _ => listDirectoryCallbackEvent.Set());
|
|
|
|
_ = sftp.EndListDirectory(asyncResult);
|
|
}
|
|
finally
|
|
{
|
|
SynchronizationContext.SetSynchronizationContext(originalContext);
|
|
}
|
|
|
|
Assert.IsTrue(listDirectoryDelegateEvent.Wait(1000));
|
|
Assert.IsTrue(listDirectoryCallbackEvent.Wait(1000));
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
[TestCategory("Sftp")]
|
|
[Description("Test passing null to BeginUploadFile")]
|
|
public void Test_Sftp_BeginUploadFile_StreamIsNull()
|
|
{
|
|
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
|
|
{
|
|
sftp.Connect();
|
|
|
|
Assert.ThrowsExactly<ArgumentNullException>(() => sftp.BeginUploadFile(null, "aaaaa", null, null));
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
[TestCategory("Sftp")]
|
|
[Description("Test passing null to BeginUploadFile")]
|
|
public void Test_Sftp_BeginUploadFile_FileNameIsWhiteSpace()
|
|
{
|
|
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
|
|
{
|
|
sftp.Connect();
|
|
|
|
Assert.ThrowsExactly<ArgumentException>(() => sftp.BeginUploadFile(new MemoryStream(), " ", null, null));
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
[TestCategory("Sftp")]
|
|
[Description("Test passing null to BeginUploadFile")]
|
|
public void Test_Sftp_BeginUploadFile_FileNameIsNull()
|
|
{
|
|
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
|
|
{
|
|
sftp.Connect();
|
|
|
|
Assert.ThrowsExactly<ArgumentNullException>(() => sftp.BeginUploadFile(new MemoryStream(), null, null, null));
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
[TestCategory("Sftp")]
|
|
public void Test_Sftp_EndUploadFile_Invalid_Async_Handle()
|
|
{
|
|
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
|
|
{
|
|
sftp.Connect();
|
|
var async1 = sftp.BeginListDirectory("/", null, null);
|
|
var filename = Path.GetTempFileName();
|
|
CreateTestFile(filename, 100);
|
|
using var fileStream = File.OpenRead(filename);
|
|
var async2 = sftp.BeginUploadFile(fileStream, "test", null, null);
|
|
|
|
Assert.ThrowsExactly<ArgumentException>(() => sftp.EndUploadFile(async1));
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
[TestCategory("Sftp")]
|
|
public async Task Test_Sftp_UploadFileAsync_UploadProgress()
|
|
{
|
|
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
|
|
{
|
|
await sftp.ConnectAsync(CancellationToken.None);
|
|
var filename = Path.GetTempFileName();
|
|
int testFileSizeMB = 1;
|
|
CreateTestFile(filename, testFileSizeMB);
|
|
using var fileStream = File.OpenRead(filename);
|
|
using ManualResetEventSlim finalCallbackCalledEvent = new();
|
|
|
|
IProgress<UploadFileProgressReport> progress = new Progress<UploadFileProgressReport>(r =>
|
|
{
|
|
if ((int)r.TotalBytesUploaded == testFileSizeMB * 1024 * 1024)
|
|
{
|
|
finalCallbackCalledEvent.Set();
|
|
}
|
|
});
|
|
|
|
await sftp.UploadFileAsync(fileStream, "test", progress);
|
|
|
|
// since the callback is queued to the thread pool, wait for the event.
|
|
bool callbackCalled = finalCallbackCalledEvent.Wait(5000);
|
|
Assert.IsTrue(callbackCalled);
|
|
}
|
|
}
|
|
|
|
private sealed class ThrowingSynchronizationContext : SynchronizationContext
|
|
{
|
|
public override void Post(SendOrPostCallback d, object state)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
public override void Send(SendOrPostCallback d, object state)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
}
|
|
}
|
|
}
|