diff --git a/Frends.FTP.UploadFiles/CHANGELOG.md b/Frends.FTP.UploadFiles/CHANGELOG.md index 44501fa..8d06038 100644 --- a/Frends.FTP.UploadFiles/CHANGELOG.md +++ b/Frends.FTP.UploadFiles/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [1.1.0] - 2023-09-12 +### Added +- Added search for local certificates from machine certification store. +- [Breaking] Added parameters ClientCertificationName and ClientCertificationThumbprint for exclusive search of client certification. + ## [1.0.2] - 2023-08-03 ### Added - Operations log improvements. diff --git a/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles.Tests/UploadFilesTests.cs b/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles.Tests/UploadFilesTests.cs index 178a6a9..9150216 100644 --- a/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles.Tests/UploadFilesTests.cs +++ b/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles.Tests/UploadFilesTests.cs @@ -1,655 +1,685 @@ -using FluentFTP; -using Frends.FTP.UploadFiles.Enums; -using Frends.FTP.UploadFiles.TaskConfiguration; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.IO; -using System.Linq; -using System.Security.Authentication; -using System.Threading; - -namespace Frends.FTP.UploadFiles.Tests; - -[TestClass] -public class UploadFilesTests -{ - private string _tempDir; - private readonly string _file = "file1.txt"; - - private Source _source = new(); - private Connection _connection = new(); - private Destination _destination = new(); - private Options _options = new(); - private Info _info = new(); - - [TestInitialize] - public void SetUp() - { - _tempDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../tempFiles"); - Directory.CreateDirectory(_tempDir); - File.WriteAllText(Path.Combine(_tempDir, _file), "test"); - Directory.CreateDirectory(Path.Combine(_tempDir, "Done")); - - _source = new() - { - Directory = _tempDir, - FileName = _file, - DirectoryToMoveAfterTransfer = Path.Combine(_tempDir, "Done"), - FileNameAfterTransfer = _file + Guid.NewGuid().ToString(), - NotFoundAction = default, - FilePaths = _tempDir, - Operation = default, - }; - - _connection = new() - { - Address = Helpers.FtpHost, - UserName = Helpers.FtpUsername, - Password = Helpers.FtpPassword, - Port = Helpers.FtpPort, - TransportType = default, - Mode = default, - KeepConnectionAliveInterval = default, - ConnectionTimeout = 60, - Encoding = default, - BufferSize = default, - UseFTPS = false, - SslMode = default, - CertificateHashStringSHA1 = "D911262984DE9CC32A3518A1094CD24249EA5C49", - EnableClientAuth = default, - ValidateAnyCertificate = default, - ClientCertificatePath = default, - SecureDataChannel = false, - }; - - _destination = new() - { - Directory = "/", - Action = default, - FileName = $"{_file}-{Guid.NewGuid()}", - }; - - _options = new() - { - CreateDestinationDirectories = true, - OperationLog = default, - PreserveLastModified = default, - RenameDestinationFileDuringTransfer = default, - RenameSourceFileBeforeTransfer = default, - ThrowErrorOnFail = default, - }; - - _info = new() - { - ProcessUri = default, - TaskExecutionID = default, - TransferName = default, - WorkDir = default, - }; - } - - [TestCleanup] - public void CleanUp() - { - if (Directory.Exists(_tempDir)) - Directory.Delete(_tempDir, true); - - var client = new FtpClient(Helpers.FtpHost, Helpers.FtpPort, Helpers.FtpUsername, Helpers.FtpPassword) - { - ConnectTimeout = 10 - }; - client.Connect(); - if (client.DirectoryExists("/")) - client.DeleteDirectory("/"); - client.Disconnect(); - client.Dispose(); - } - - [TestMethod] - public void UploadFTPS_IncorrectFingerprint() - { - var connection = _connection; - connection.SslMode = FtpsSslMode.Explicit; - connection.UseFTPS = true; - connection.CertificateHashStringSHA1 = "incorrect"; - - // Test and assert - var ex = Assert.ThrowsException(() => FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken())); - Assert.AreEqual(1, ex.InnerExceptions.Count); - Assert.AreEqual(typeof(AuthenticationException), ex.InnerExceptions[0].GetType()); - } - - [TestMethod] - public void UploadFTPS_IncorrectAuth() - { - var connectionA = _connection; - connectionA.Address = null; - - var connectionB = _connection; - connectionB.Port = 0; - - var connectionC = _connection; - connectionC.Password = null; - - var connectionD = _connection; - connectionD.UserName = null; - - var ex1 = Assert.ThrowsException(() => FTP.UploadFiles(_source, _destination, connectionA, _options, _info, new CancellationToken())); - Assert.AreEqual(typeof(NullReferenceException), ex1.GetType()); - - var ex2 = Assert.ThrowsException(() => FTP.UploadFiles(_source, _destination, connectionB, _options, _info, new CancellationToken())); - Assert.AreEqual(typeof(NullReferenceException), ex2.GetType()); - - var ex3 = Assert.ThrowsException(() => FTP.UploadFiles(_source, _destination, connectionC, _options, _info, new CancellationToken())); - Assert.AreEqual(typeof(NullReferenceException), ex3.GetType()); - - var ex4 = Assert.ThrowsException(() => FTP.UploadFiles(_source, _destination, connectionD, _options, _info, new CancellationToken())); - Assert.AreEqual(typeof(NullReferenceException), ex4.GetType()); - } - - [TestMethod] - public void UploadFTP_UploadFile() - { - var result = FTP.UploadFiles(_source, _destination, _connection, _options, _info, new CancellationToken()); - Assert.IsTrue(result.Success); - Assert.AreEqual(1, result.SuccessfulTransferCount); - } - - [TestMethod] - public void UploadFTPS_DestinationActions() - { - var destinationActions = new[] { DestinationAction.Error, DestinationAction.Overwrite, DestinationAction.Append }; - - foreach (var destinationAction in destinationActions) - { - var destination = _destination; - destination.Action = destinationAction; - - var result = FTP.UploadFiles(_source, destination, _connection, _options, _info, new CancellationToken()); - Assert.IsTrue(result.Success, $"DestinationAction: {destinationAction}"); - Assert.IsFalse(result.ActionSkipped, $"DestinationAction: {destinationAction}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"DestinationAction: {destinationAction}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"DestinationAction: {destinationAction}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"DestinationAction: {destinationAction}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"DestinationAction: {destinationAction}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"DestinationAction: {destinationAction}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"DestinationAction: {destinationAction}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_FtpMode() - { - var ftpModes = new[] { FtpMode.Active, FtpMode.Passive }; - - foreach (var ftpMode in ftpModes) - { - var connection = _connection; - connection.Mode = ftpMode; - - var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); - if (ftpMode is FtpMode.Active) - { - Assert.IsFalse(result.Success); - Assert.IsTrue(result.UserResultMessage.Contains("Error: Error while uploading the file to the server. See InnerException for more info."), $"FtpMode: {ftpMode}"); - Assert.IsFalse(result.ActionSkipped, $"FtpMode: {ftpMode}"); - Assert.AreEqual(0, result.SuccessfulTransferCount, $"FtpMode: {ftpMode}"); - Assert.AreEqual(1, result.TransferErrors.Count, $"FtpMode: {ftpMode}"); - Assert.AreEqual(0, result.TransferredFileNames.Count(), $"FtpMode: {ftpMode}"); - Assert.AreEqual(0, result.TransferredFilePaths.Count(), $"FtpMode: {ftpMode}"); - } - else - { - Assert.IsTrue(result.Success, $"FtpMode: {ftpMode}"); - Assert.IsFalse(result.ActionSkipped, $"FtpMode: {ftpMode}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"FtpMode: {ftpMode}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"FtpMode: {ftpMode}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"FtpMode: {ftpMode}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"FtpMode: {ftpMode}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"FtpMode: {ftpMode}"); - } - Assert.IsTrue(result.OperationsLog.Count < 4, $"FtpMode: {ftpMode}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_FtpsSslModes() - { - var ftpsSslModes = new[] { FtpsSslMode.None, FtpsSslMode.Explicit, FtpsSslMode.Auto }; - - foreach (var ftpsSslMode in ftpsSslModes) - { - var connection = _connection; - connection.SslMode = ftpsSslMode; - connection.UseFTPS = true; - - var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); - Assert.IsTrue(result.Success, $"FtpsSslMode: {ftpsSslMode}"); - Assert.IsFalse(result.ActionSkipped, $"FtpsSslMode: {ftpsSslMode}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"FtpsSslMode: {ftpsSslMode}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"FtpsSslMode: {ftpsSslMode}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"FtpsSslMode: {ftpsSslMode}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"FtpsSslMode: {ftpsSslMode}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"FtpsSslMode: {ftpsSslMode}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"FtpsSslMode: {ftpsSslMode}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_SecureDataChannel() - { - var boo = new[] { true, false }; - - foreach (var bo in boo) - { - var connection = _connection; - connection.SecureDataChannel = bo; - - var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); - Assert.IsTrue(result.Success, $"Bo: {bo}"); - Assert.IsFalse(result.ActionSkipped, $"Bo: {bo}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"Bo: {bo}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"Bo: {bo}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"Bo: {bo}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"Bo: {bo}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"Bo: {bo}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"Bo: {bo}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_FtpTransportTypes() - { - var ftpTransportTypes = new[] { FtpTransportType.Binary, FtpTransportType.Ascii }; - - foreach (var ftpTransportType in ftpTransportTypes) - { - var connection = _connection; - connection.TransportType = ftpTransportType; - - var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); - Assert.IsTrue(result.Success, $"FtpTransportType: {ftpTransportType}"); - Assert.IsFalse(result.ActionSkipped, $"FtpTransportType: {ftpTransportType}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"FtpTransportType: {ftpTransportType}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"FtpTransportType: {ftpTransportType}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"FtpTransportType: {ftpTransportType}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"FtpTransportType: {ftpTransportType}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"FtpTransportType: {ftpTransportType}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"FtpTransportType: {ftpTransportType}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_NotFoundActions() - { - var notFoundActions = new[] { SourceNotFoundAction.Error, SourceNotFoundAction.Ignore, SourceNotFoundAction.Info }; - - foreach (var notFoundAction in notFoundActions) - { - var source = _source; - source.NotFoundAction = notFoundAction; - source.FileName = ""; - - var result = FTP.UploadFiles(source, _destination, _connection, _options, _info, new CancellationToken()); - - if (notFoundAction is SourceNotFoundAction.Error) - { - Assert.IsFalse(result.Success, $"action: {notFoundAction}"); - Assert.IsFalse(result.ActionSkipped, $"action: {notFoundAction}"); - Assert.AreEqual(0, result.SuccessfulTransferCount, $"action: {notFoundAction}"); - Assert.AreEqual(1, result.TransferErrors.Count, $"action: {notFoundAction}"); - Assert.AreEqual(0, result.TransferredFileNames.Count(), $"action: {notFoundAction}"); - Assert.AreEqual(0, result.TransferredFilePaths.Count(), $"action: {notFoundAction}"); - - } - else - { - Assert.IsTrue(result.Success, $"action: {notFoundAction}"); - Assert.IsTrue(result.ActionSkipped, $"action: {notFoundAction}"); - Assert.AreEqual(0, result.SuccessfulTransferCount, $"action: {notFoundAction}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"action: {notFoundAction}"); - Assert.AreEqual(0, result.TransferredFileNames.Count(), $"action: {notFoundAction}"); - Assert.AreEqual(0, result.TransferredFilePaths.Count(), $"action: {notFoundAction}"); - - } - Assert.IsTrue(result.UserResultMessage.Contains("No source files found"), $"action: {notFoundAction}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"action: {notFoundAction}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_SourceOperations() - { - var sourceOperations = new[] { SourceOperation.Move, SourceOperation.Delete, SourceOperation.Rename, SourceOperation.Nothing }; - - foreach (var sourceOperation in sourceOperations) - { - var source = _source; - source.Operation = sourceOperation; - - var result = FTP.UploadFiles(source, _destination, _connection, _options, _info, new CancellationToken()); - Assert.IsTrue(result.Success, $"SourceOperation: {sourceOperation}"); - Assert.IsFalse(result.ActionSkipped, $"SourceOperation: {sourceOperation}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"SourceOperation: {sourceOperation}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"SourceOperation: {sourceOperation}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"SourceOperation: {sourceOperation}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"SourceOperation: {sourceOperation}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"SourceOperation: {sourceOperation}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"SourceOperation: {sourceOperation}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_FileEncodings() - { - var fileEncodings = new[] { "UTF-8", "ASCII", "foobar123", string.Empty }; - - foreach (var fileEncoding in fileEncodings) - { - var connection = _connection; - connection.Encoding = fileEncoding; - - if (connection.Encoding.Equals("foobar123")) - { - var ex = Assert.ThrowsException(() => FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken())); - Assert.IsTrue(ex.Message.Contains("is not a supported encoding name"), $"FileEncoding: {fileEncoding}"); - } - else - { - var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); - Assert.IsTrue(result.Success, $"FileEncoding: {fileEncoding}"); - Assert.IsFalse(result.ActionSkipped, $"FileEncoding: {fileEncoding}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"FileEncoding: {fileEncoding}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"FileEncoding: {fileEncoding}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"FileEncoding: {fileEncoding}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"FileEncoding: {fileEncoding}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"FileEncoding: {fileEncoding}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"FileEncoding: {fileEncoding}"); - } - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_Port() - { - var ports = new[] { 0, 21, 210 }; - - foreach (var port in ports) - { - var connection = _connection; - connection.Port = port; - - var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); - if (port == 210) - { - Assert.IsFalse(result.Success, $"Port: {port}"); - Assert.IsTrue(result.ActionSkipped, $"Port: {port}"); - Assert.AreEqual(0, result.SuccessfulTransferCount, $"Port: {port}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"Port: {port}"); - Assert.AreEqual(0, result.TransferredFileNames.Count(), $"Port: {port}"); - Assert.AreEqual(0, result.TransferredFilePaths.Count(), $"Port: {port}"); - Assert.IsTrue(result.UserResultMessage.Contains("Unable to establish the socket: No such host is known."), $"Port: {port}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"Port: {port}"); - } - else - { - Assert.IsTrue(result.Success, $"Port: {port}"); - Assert.IsFalse(result.ActionSkipped, $"Port: {port}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"Port: {port}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"Port: {port}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"Port: {port}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"Port: {port}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"Port: {port}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"Port: {port}"); - } - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_KeepConnectionAliveInterval() - { - var intervals = new[] { 0, 1, 100 }; - - foreach (var interval in intervals) - { - var connection = _connection; - connection.KeepConnectionAliveInterval = interval; - - var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); - Assert.IsTrue(result.Success, $"Interval: {interval}"); - Assert.IsFalse(result.ActionSkipped, $"Interval: {interval}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"Interval: {interval}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"Interval: {interval}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"Interval: {interval}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"Interval: {interval}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"Interval: {interval}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"Interval: {interval}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_ConnectionTimeout() - { - var timeouts = new[] { 1, 100 }; - - foreach (var timeout in timeouts) - { - var connection = _connection; - connection.ConnectionTimeout = timeout; - - var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); - Assert.IsTrue(result.Success, $"Timeout: {timeout}"); - Assert.IsFalse(result.ActionSkipped, $"Timeout: {timeout}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"Timeout: {timeout}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"Timeout: {timeout}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"Timeout: {timeout}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"Timeout: {timeout}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"Timeout: {timeout}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"Timeout: {timeout}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_BufferSize() - { - var sizes = new[] { 1, 100, 1000, 10000 }; - - foreach (var size in sizes) - { - var connection = _connection; - connection.BufferSize = size; - - var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); - Assert.IsTrue(result.Success, $"Size: {size}"); - Assert.IsFalse(result.ActionSkipped, $"Size: {size}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"Size: {size}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"Size: {size}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"Size: {size}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"Size: {size}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"Size: {size}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"Size: {size}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_ValidateAnyCertificate() - { - var boo = new[] { true, false }; - - foreach (var bo in boo) - { - var connection = _connection; - connection.ValidateAnyCertificate = bo; - - var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); - Assert.IsTrue(result.Success, $"bo: {bo}"); - Assert.IsFalse(result.ActionSkipped, $"bo: {bo}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"bo: {bo}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"bo: {bo}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"bo: {bo}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"bo: {bo}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"bo: {bo}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"bo: {bo}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_FileNameAfterTransfer_Missing() - { - var source = _source; - source.FileNameAfterTransfer = ""; - - var result = FTP.UploadFiles(source, _destination, _connection, _options, _info, new CancellationToken()); - - Assert.IsTrue(result.Success); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred")); - Assert.IsFalse(result.ActionSkipped); - Assert.AreEqual(1, result.SuccessfulTransferCount); - Assert.AreEqual(0, result.TransferErrors.Count); - Assert.AreEqual(1, result.TransferredFileNames.Count()); - Assert.AreEqual(1, result.TransferredFilePaths.Count()); - Assert.IsTrue(result.OperationsLog.Count < 4); - } - - [TestMethod] - public void UploadFTPS_DirectoryToMoveAfterTransfer_Missing() - { - var sourceOperations = new[] { SourceOperation.Move, SourceOperation.Delete, SourceOperation.Rename, SourceOperation.Nothing }; - - foreach (var sourceOperation in sourceOperations) - { - var source = _source; - source.DirectoryToMoveAfterTransfer = "DoesntExists"; - source.Operation = sourceOperation; - - var result = FTP.UploadFiles(source, _destination, _connection, _options, _info, new CancellationToken()); - - if (sourceOperation is SourceOperation.Move) - { - Assert.IsFalse(result.Success, $"operation: {sourceOperation}"); - Assert.IsTrue(result.UserResultMessage.Contains("not find"), $"operation: {sourceOperation}"); - Assert.IsFalse(result.ActionSkipped, $"operation: {sourceOperation}"); - Assert.AreEqual(0, result.SuccessfulTransferCount, $"operation: {sourceOperation}"); - Assert.AreEqual(1, result.TransferErrors.Count, $"operation: {sourceOperation}"); - Assert.AreEqual(0, result.TransferredFileNames.Count(), $"operation: {sourceOperation}"); - Assert.AreEqual(0, result.TransferredFilePaths.Count(), $"operation: {sourceOperation}"); - } - else - { - Assert.IsTrue(result.Success, $"operation: {sourceOperation}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"operation: {sourceOperation}"); - Assert.IsFalse(result.ActionSkipped, $"operation: {sourceOperation}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"operation: {sourceOperation}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"operation: {sourceOperation}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"operation: {sourceOperation}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"operation: {sourceOperation}"); - } - Assert.IsTrue(result.OperationsLog.Count < 4, $"operation: {sourceOperation}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_ThrowErrorOnFail() - { - var source = _source; - source.FileName = "DoesntExists"; - - var options = _options; - options.ThrowErrorOnFail = true; - - var ex = Assert.ThrowsException(() => FTP.UploadFiles(source, _destination, _connection, options, _info, new CancellationToken())); - Assert.AreEqual(typeof(Exception), ex.GetType()); - } - - [TestMethod] - public void UploadFTPS_PreserveLastModified() - { - var boo = new[] { true, false }; - - foreach (var bo in boo) - { - var options = _options; - options.PreserveLastModified = bo; - - var result = FTP.UploadFiles(_source, _destination, _connection, options, _info, new CancellationToken()); - Assert.IsTrue(result.Success, $"bo: {bo}"); - Assert.IsFalse(result.ActionSkipped, $"bo: {bo}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"bo: {bo}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"bo: {bo}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"bo: {bo}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"bo: {bo}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"bo: {bo}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"bo: {bo}"); - - CleanUp(); - SetUp(); - } - } - - [TestMethod] - public void UploadFTPS_OperationLog() - { - var boo = new[] { true, false }; - - foreach (var bo in boo) - { - var options = _options; - options.OperationLog = bo; - - var result = FTP.UploadFiles(_source, _destination, _connection, options, _info, new CancellationToken()); - Assert.IsTrue(result.Success, $"bo: {bo}"); - Assert.IsFalse(result.ActionSkipped, $"bo: {bo}"); - Assert.AreEqual(1, result.SuccessfulTransferCount, $"bo: {bo}"); - Assert.AreEqual(0, result.TransferErrors.Count, $"bo: {bo}"); - Assert.AreEqual(1, result.TransferredFileNames.Count(), $"bo: {bo}"); - Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"bo: {bo}"); - Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"bo: {bo}"); - Assert.IsTrue(result.OperationsLog.Count < 4, $"bo: {bo}"); - - CleanUp(); - SetUp(); - } - } +using FluentFTP; +using Frends.FTP.UploadFiles.Enums; +using Frends.FTP.UploadFiles.TaskConfiguration; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Linq; +using System.Security.Authentication; +using System.Threading; + +namespace Frends.FTP.UploadFiles.Tests; + +[TestClass] +public class UploadFilesTests +{ + private string _tempDir; + private readonly string _file = "file1.txt"; + + private Source _source = new(); + private Connection _connection = new(); + private Destination _destination = new(); + private Options _options = new(); + private Info _info = new(); + + [TestInitialize] + public void SetUp() + { + _tempDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../tempFiles"); + Directory.CreateDirectory(_tempDir); + File.WriteAllText(Path.Combine(_tempDir, _file), "test"); + Directory.CreateDirectory(Path.Combine(_tempDir, "Done")); + + _source = new() + { + Directory = _tempDir, + FileName = _file, + DirectoryToMoveAfterTransfer = Path.Combine(_tempDir, "Done"), + FileNameAfterTransfer = _file + Guid.NewGuid().ToString(), + NotFoundAction = default, + FilePaths = _tempDir, + Operation = default, + }; + + _connection = new() + { + Address = Helpers.FtpHost, + UserName = Helpers.FtpUsername, + Password = Helpers.FtpPassword, + Port = Helpers.FtpPort, + TransportType = default, + Mode = default, + KeepConnectionAliveInterval = default, + ConnectionTimeout = 60, + Encoding = default, + BufferSize = default, + UseFTPS = false, + SslMode = default, + CertificateHashStringSHA1 = "D911262984DE9CC32A3518A1094CD24249EA5C49", + EnableClientAuth = default, + ValidateAnyCertificate = default, + ClientCertificatePath = default, + SecureDataChannel = false, + }; + + _destination = new() + { + Directory = "/", + Action = default, + FileName = $"{_file}-{Guid.NewGuid()}", + }; + + _options = new() + { + CreateDestinationDirectories = true, + OperationLog = default, + PreserveLastModified = default, + RenameDestinationFileDuringTransfer = default, + RenameSourceFileBeforeTransfer = default, + ThrowErrorOnFail = default, + }; + + _info = new() + { + ProcessUri = default, + TaskExecutionID = default, + TransferName = default, + WorkDir = default, + }; + } + + [TestCleanup] + public void CleanUp() + { + if (Directory.Exists(_tempDir)) + Directory.Delete(_tempDir, true); + + var client = new FtpClient(Helpers.FtpHost, Helpers.FtpPort, Helpers.FtpUsername, Helpers.FtpPassword) + { + ConnectTimeout = 10 + }; + client.Connect(); + if (client.DirectoryExists("/")) + client.DeleteDirectory("/"); + client.Disconnect(); + client.Dispose(); + } + + [TestMethod] + public void UploadFTPS_IncorrectFingerprint() + { + var connection = _connection; + connection.SslMode = FtpsSslMode.Explicit; + connection.UseFTPS = true; + connection.CertificateHashStringSHA1 = "incorrect"; + + // Test and assert + var ex = Assert.ThrowsException(() => FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken())); + Assert.AreEqual(1, ex.InnerExceptions.Count); + Assert.AreEqual(typeof(AuthenticationException), ex.InnerExceptions[0].GetType()); + } + + [TestMethod] + public void UploadFTPS_IncorrectAuth() + { + var connectionA = _connection; + connectionA.Address = null; + + var connectionB = _connection; + connectionB.Port = 0; + + var connectionC = _connection; + connectionC.Password = null; + + var connectionD = _connection; + connectionD.UserName = null; + + var ex1 = Assert.ThrowsException(() => FTP.UploadFiles(_source, _destination, connectionA, _options, _info, new CancellationToken())); + Assert.AreEqual(typeof(NullReferenceException), ex1.GetType()); + + var ex2 = Assert.ThrowsException(() => FTP.UploadFiles(_source, _destination, connectionB, _options, _info, new CancellationToken())); + Assert.AreEqual(typeof(NullReferenceException), ex2.GetType()); + + var ex3 = Assert.ThrowsException(() => FTP.UploadFiles(_source, _destination, connectionC, _options, _info, new CancellationToken())); + Assert.AreEqual(typeof(NullReferenceException), ex3.GetType()); + + var ex4 = Assert.ThrowsException(() => FTP.UploadFiles(_source, _destination, connectionD, _options, _info, new CancellationToken())); + Assert.AreEqual(typeof(NullReferenceException), ex4.GetType()); + } + + [TestMethod] + public void UploadFTP_UploadFile() + { + var result = FTP.UploadFiles(_source, _destination, _connection, _options, _info, new CancellationToken()); + Assert.IsTrue(result.Success); + Assert.AreEqual(1, result.SuccessfulTransferCount); + } + + [TestMethod] + public void UploadFTPS_DestinationActions() + { + var destinationActions = new[] { DestinationAction.Error, DestinationAction.Overwrite, DestinationAction.Append }; + + foreach (var destinationAction in destinationActions) + { + var destination = _destination; + destination.Action = destinationAction; + + var result = FTP.UploadFiles(_source, destination, _connection, _options, _info, new CancellationToken()); + Assert.IsTrue(result.Success, $"DestinationAction: {destinationAction}"); + Assert.IsFalse(result.ActionSkipped, $"DestinationAction: {destinationAction}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"DestinationAction: {destinationAction}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"DestinationAction: {destinationAction}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"DestinationAction: {destinationAction}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"DestinationAction: {destinationAction}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"DestinationAction: {destinationAction}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"DestinationAction: {destinationAction}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_FtpMode() + { + var ftpModes = new[] { FtpMode.Active, FtpMode.Passive }; + + foreach (var ftpMode in ftpModes) + { + var connection = _connection; + connection.Mode = ftpMode; + + var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); + if (ftpMode is FtpMode.Active) + { + Assert.IsFalse(result.Success); + Assert.IsTrue(result.UserResultMessage.Contains("Error: Error while uploading the file to the server. See InnerException for more info."), $"FtpMode: {ftpMode}"); + Assert.IsFalse(result.ActionSkipped, $"FtpMode: {ftpMode}"); + Assert.AreEqual(0, result.SuccessfulTransferCount, $"FtpMode: {ftpMode}"); + Assert.AreEqual(1, result.TransferErrors.Count, $"FtpMode: {ftpMode}"); + Assert.AreEqual(0, result.TransferredFileNames.Count(), $"FtpMode: {ftpMode}"); + Assert.AreEqual(0, result.TransferredFilePaths.Count(), $"FtpMode: {ftpMode}"); + } + else + { + Assert.IsTrue(result.Success, $"FtpMode: {ftpMode}"); + Assert.IsFalse(result.ActionSkipped, $"FtpMode: {ftpMode}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"FtpMode: {ftpMode}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"FtpMode: {ftpMode}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"FtpMode: {ftpMode}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"FtpMode: {ftpMode}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"FtpMode: {ftpMode}"); + } + Assert.IsTrue(result.OperationsLog.Count < 4, $"FtpMode: {ftpMode}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_FtpsSslModes() + { + var ftpsSslModes = new[] { FtpsSslMode.None, FtpsSslMode.Explicit, FtpsSslMode.Auto }; + + foreach (var ftpsSslMode in ftpsSslModes) + { + var connection = _connection; + connection.SslMode = ftpsSslMode; + connection.UseFTPS = true; + + var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); + Assert.IsTrue(result.Success, $"FtpsSslMode: {ftpsSslMode}"); + Assert.IsFalse(result.ActionSkipped, $"FtpsSslMode: {ftpsSslMode}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"FtpsSslMode: {ftpsSslMode}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"FtpsSslMode: {ftpsSslMode}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"FtpsSslMode: {ftpsSslMode}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"FtpsSslMode: {ftpsSslMode}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"FtpsSslMode: {ftpsSslMode}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"FtpsSslMode: {ftpsSslMode}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_SecureDataChannel() + { + var boo = new[] { true, false }; + + foreach (var bo in boo) + { + var connection = _connection; + connection.SecureDataChannel = bo; + + var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); + Assert.IsTrue(result.Success, $"Bo: {bo}"); + Assert.IsFalse(result.ActionSkipped, $"Bo: {bo}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"Bo: {bo}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"Bo: {bo}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"Bo: {bo}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"Bo: {bo}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"Bo: {bo}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"Bo: {bo}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_FtpTransportTypes() + { + var ftpTransportTypes = new[] { FtpTransportType.Binary, FtpTransportType.Ascii }; + + foreach (var ftpTransportType in ftpTransportTypes) + { + var connection = _connection; + connection.TransportType = ftpTransportType; + + var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); + Assert.IsTrue(result.Success, $"FtpTransportType: {ftpTransportType}"); + Assert.IsFalse(result.ActionSkipped, $"FtpTransportType: {ftpTransportType}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"FtpTransportType: {ftpTransportType}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"FtpTransportType: {ftpTransportType}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"FtpTransportType: {ftpTransportType}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"FtpTransportType: {ftpTransportType}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"FtpTransportType: {ftpTransportType}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"FtpTransportType: {ftpTransportType}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_NotFoundActions() + { + var notFoundActions = new[] { SourceNotFoundAction.Error, SourceNotFoundAction.Ignore, SourceNotFoundAction.Info }; + + foreach (var notFoundAction in notFoundActions) + { + var source = _source; + source.NotFoundAction = notFoundAction; + source.FileName = ""; + + var result = FTP.UploadFiles(source, _destination, _connection, _options, _info, new CancellationToken()); + + if (notFoundAction is SourceNotFoundAction.Error) + { + Assert.IsFalse(result.Success, $"action: {notFoundAction}"); + Assert.IsFalse(result.ActionSkipped, $"action: {notFoundAction}"); + Assert.AreEqual(0, result.SuccessfulTransferCount, $"action: {notFoundAction}"); + Assert.AreEqual(1, result.TransferErrors.Count, $"action: {notFoundAction}"); + Assert.AreEqual(0, result.TransferredFileNames.Count(), $"action: {notFoundAction}"); + Assert.AreEqual(0, result.TransferredFilePaths.Count(), $"action: {notFoundAction}"); + + } + else + { + Assert.IsTrue(result.Success, $"action: {notFoundAction}"); + Assert.IsTrue(result.ActionSkipped, $"action: {notFoundAction}"); + Assert.AreEqual(0, result.SuccessfulTransferCount, $"action: {notFoundAction}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"action: {notFoundAction}"); + Assert.AreEqual(0, result.TransferredFileNames.Count(), $"action: {notFoundAction}"); + Assert.AreEqual(0, result.TransferredFilePaths.Count(), $"action: {notFoundAction}"); + + } + Assert.IsTrue(result.UserResultMessage.Contains("No source files found"), $"action: {notFoundAction}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"action: {notFoundAction}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_SourceOperations() + { + var sourceOperations = new[] { SourceOperation.Move, SourceOperation.Delete, SourceOperation.Rename, SourceOperation.Nothing }; + + foreach (var sourceOperation in sourceOperations) + { + var source = _source; + source.Operation = sourceOperation; + + var result = FTP.UploadFiles(source, _destination, _connection, _options, _info, new CancellationToken()); + Assert.IsTrue(result.Success, $"SourceOperation: {sourceOperation}"); + Assert.IsFalse(result.ActionSkipped, $"SourceOperation: {sourceOperation}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"SourceOperation: {sourceOperation}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"SourceOperation: {sourceOperation}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"SourceOperation: {sourceOperation}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"SourceOperation: {sourceOperation}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"SourceOperation: {sourceOperation}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"SourceOperation: {sourceOperation}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_FileEncodings() + { + var fileEncodings = new[] { "UTF-8", "ASCII", "foobar123", string.Empty }; + + foreach (var fileEncoding in fileEncodings) + { + var connection = _connection; + connection.Encoding = fileEncoding; + + if (connection.Encoding.Equals("foobar123")) + { + var ex = Assert.ThrowsException(() => FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken())); + Assert.IsTrue(ex.Message.Contains("is not a supported encoding name"), $"FileEncoding: {fileEncoding}"); + } + else + { + var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); + Assert.IsTrue(result.Success, $"FileEncoding: {fileEncoding}"); + Assert.IsFalse(result.ActionSkipped, $"FileEncoding: {fileEncoding}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"FileEncoding: {fileEncoding}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"FileEncoding: {fileEncoding}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"FileEncoding: {fileEncoding}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"FileEncoding: {fileEncoding}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"FileEncoding: {fileEncoding}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"FileEncoding: {fileEncoding}"); + } + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_Port() + { + var ports = new[] { 0, 21, 210 }; + + foreach (var port in ports) + { + var connection = _connection; + connection.Port = port; + + var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); + if (port == 210) + { + Assert.IsFalse(result.Success, $"Port: {port}"); + Assert.IsTrue(result.ActionSkipped, $"Port: {port}"); + Assert.AreEqual(0, result.SuccessfulTransferCount, $"Port: {port}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"Port: {port}"); + Assert.AreEqual(0, result.TransferredFileNames.Count(), $"Port: {port}"); + Assert.AreEqual(0, result.TransferredFilePaths.Count(), $"Port: {port}"); + Assert.IsTrue(result.UserResultMessage.Contains("Unable to establish the socket: No such host is known."), $"Port: {port}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"Port: {port}"); + } + else + { + Assert.IsTrue(result.Success, $"Port: {port}"); + Assert.IsFalse(result.ActionSkipped, $"Port: {port}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"Port: {port}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"Port: {port}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"Port: {port}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"Port: {port}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"Port: {port}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"Port: {port}"); + } + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_KeepConnectionAliveInterval() + { + var intervals = new[] { 0, 1, 100 }; + + foreach (var interval in intervals) + { + var connection = _connection; + connection.KeepConnectionAliveInterval = interval; + + var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); + Assert.IsTrue(result.Success, $"Interval: {interval}"); + Assert.IsFalse(result.ActionSkipped, $"Interval: {interval}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"Interval: {interval}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"Interval: {interval}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"Interval: {interval}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"Interval: {interval}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"Interval: {interval}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"Interval: {interval}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_ConnectionTimeout() + { + var timeouts = new[] { 1, 100 }; + + foreach (var timeout in timeouts) + { + var connection = _connection; + connection.ConnectionTimeout = timeout; + + var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); + Assert.IsTrue(result.Success, $"Timeout: {timeout}"); + Assert.IsFalse(result.ActionSkipped, $"Timeout: {timeout}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"Timeout: {timeout}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"Timeout: {timeout}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"Timeout: {timeout}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"Timeout: {timeout}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"Timeout: {timeout}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"Timeout: {timeout}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_BufferSize() + { + var sizes = new[] { 1, 100, 1000, 10000 }; + + foreach (var size in sizes) + { + var connection = _connection; + connection.BufferSize = size; + + var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); + Assert.IsTrue(result.Success, $"Size: {size}"); + Assert.IsFalse(result.ActionSkipped, $"Size: {size}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"Size: {size}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"Size: {size}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"Size: {size}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"Size: {size}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"Size: {size}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"Size: {size}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_ValidateAnyCertificate() + { + var boo = new[] { true, false }; + + foreach (var bo in boo) + { + var connection = _connection; + connection.ValidateAnyCertificate = bo; + + var result = FTP.UploadFiles(_source, _destination, connection, _options, _info, new CancellationToken()); + Assert.IsTrue(result.Success, $"bo: {bo}"); + Assert.IsFalse(result.ActionSkipped, $"bo: {bo}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"bo: {bo}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"bo: {bo}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"bo: {bo}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"bo: {bo}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"bo: {bo}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"bo: {bo}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_FileNameAfterTransfer_Missing() + { + var source = _source; + source.FileNameAfterTransfer = ""; + + var result = FTP.UploadFiles(source, _destination, _connection, _options, _info, new CancellationToken()); + + Assert.IsTrue(result.Success); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred")); + Assert.IsFalse(result.ActionSkipped); + Assert.AreEqual(1, result.SuccessfulTransferCount); + Assert.AreEqual(0, result.TransferErrors.Count); + Assert.AreEqual(1, result.TransferredFileNames.Count()); + Assert.AreEqual(1, result.TransferredFilePaths.Count()); + Assert.IsTrue(result.OperationsLog.Count < 4); + } + + [TestMethod] + public void UploadFTPS_DirectoryToMoveAfterTransfer_Missing() + { + var sourceOperations = new[] { SourceOperation.Move, SourceOperation.Delete, SourceOperation.Rename, SourceOperation.Nothing }; + + foreach (var sourceOperation in sourceOperations) + { + var source = _source; + source.DirectoryToMoveAfterTransfer = "DoesntExists"; + source.Operation = sourceOperation; + + var result = FTP.UploadFiles(source, _destination, _connection, _options, _info, new CancellationToken()); + + if (sourceOperation is SourceOperation.Move) + { + Assert.IsFalse(result.Success, $"operation: {sourceOperation}"); + Assert.IsTrue(result.UserResultMessage.Contains("not find"), $"operation: {sourceOperation}"); + Assert.IsFalse(result.ActionSkipped, $"operation: {sourceOperation}"); + Assert.AreEqual(0, result.SuccessfulTransferCount, $"operation: {sourceOperation}"); + Assert.AreEqual(1, result.TransferErrors.Count, $"operation: {sourceOperation}"); + Assert.AreEqual(0, result.TransferredFileNames.Count(), $"operation: {sourceOperation}"); + Assert.AreEqual(0, result.TransferredFilePaths.Count(), $"operation: {sourceOperation}"); + } + else + { + Assert.IsTrue(result.Success, $"operation: {sourceOperation}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"operation: {sourceOperation}"); + Assert.IsFalse(result.ActionSkipped, $"operation: {sourceOperation}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"operation: {sourceOperation}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"operation: {sourceOperation}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"operation: {sourceOperation}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"operation: {sourceOperation}"); + } + Assert.IsTrue(result.OperationsLog.Count < 4, $"operation: {sourceOperation}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_ThrowErrorOnFail() + { + var source = _source; + source.FileName = "DoesntExists"; + + var options = _options; + options.ThrowErrorOnFail = true; + + var ex = Assert.ThrowsException(() => FTP.UploadFiles(source, _destination, _connection, options, _info, new CancellationToken())); + Assert.AreEqual(typeof(Exception), ex.GetType()); + } + + [TestMethod] + public void UploadFTPS_PreserveLastModified() + { + var boo = new[] { true, false }; + + foreach (var bo in boo) + { + var options = _options; + options.PreserveLastModified = bo; + + var result = FTP.UploadFiles(_source, _destination, _connection, options, _info, new CancellationToken()); + Assert.IsTrue(result.Success, $"bo: {bo}"); + Assert.IsFalse(result.ActionSkipped, $"bo: {bo}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"bo: {bo}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"bo: {bo}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"bo: {bo}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"bo: {bo}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"bo: {bo}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"bo: {bo}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_OperationLog() + { + var boo = new[] { true, false }; + + foreach (var bo in boo) + { + var options = _options; + options.OperationLog = bo; + + var result = FTP.UploadFiles(_source, _destination, _connection, options, _info, new CancellationToken()); + Assert.IsTrue(result.Success, $"bo: {bo}"); + Assert.IsFalse(result.ActionSkipped, $"bo: {bo}"); + Assert.AreEqual(1, result.SuccessfulTransferCount, $"bo: {bo}"); + Assert.AreEqual(0, result.TransferErrors.Count, $"bo: {bo}"); + Assert.AreEqual(1, result.TransferredFileNames.Count(), $"bo: {bo}"); + Assert.AreEqual(1, result.TransferredFilePaths.Count(), $"bo: {bo}"); + Assert.IsTrue(result.UserResultMessage.Contains("files transferred"), $"bo: {bo}"); + Assert.IsTrue(result.OperationsLog.Count < 4, $"bo: {bo}"); + + CleanUp(); + SetUp(); + } + } + + [TestMethod] + public void UploadFTPS_CurrentUserHasNoCertificates() + { + var connection = new Connection + { + Address = Helpers.FtpHost, + UserName = Helpers.FtpUsername, + Password = Helpers.FtpPassword, + Port = Helpers.FtpsPort, + SslMode = FtpsSslMode.Explicit, + EnableClientAuth = true, + UseFTPS = true, + ValidateAnyCertificate = false, + CertificateHashStringSHA1 = "", + ClientCertificatePath = "", + ClientCertificateName = "", + ClientCertificateThumbprint = "" + }; + + var ex = Assert.ThrowsException(() => + { + var result = FTP.UploadFiles(_source, _destination, connection, new Options(), new Info(), + default); + + }); + + Assert.AreEqual(1, ex.InnerExceptions.Count); + Assert.AreEqual(typeof(AuthenticationException), ex.InnerExceptions[0].GetType()); + } } \ No newline at end of file diff --git a/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles/Definitions/FileTransporter.cs b/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles/Definitions/FileTransporter.cs index 8adb481..f3e9beb 100644 --- a/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles/Definitions/FileTransporter.cs +++ b/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles/Definitions/FileTransporter.cs @@ -1,375 +1,398 @@ -using FluentFTP; -using Frends.FTP.UploadFiles.Enums; -using Frends.FTP.UploadFiles.Logging; -using Frends.FTP.UploadFiles.TaskConfiguration; -using Frends.FTP.UploadFiles.TaskResult; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading; - -namespace Frends.FTP.UploadFiles.Definitions -{ - /// - /// Main class for FTP file transfers - /// - internal class FileTransporter - { - private readonly IFtpLogger _logger; - private readonly BatchContext _batchContext; - private readonly string[] _filePaths; - private readonly RenamingPolicy _renamingPolicy; - private readonly string _sourceDirectoryWithMacrosExpanded; - private readonly string _destinationDirectoryWithMacrosExpanded; - - public FileTransporter(IFtpLogger logger, BatchContext context, Guid instanceId) - { - _logger = logger; - _batchContext = context; - _renamingPolicy = new RenamingPolicy(_batchContext.Info.TransferName, instanceId); - - _sourceDirectoryWithMacrosExpanded = - _renamingPolicy.ExpandDirectoryForMacros(_batchContext.Source.Directory); - _destinationDirectoryWithMacrosExpanded = - _renamingPolicy.ExpandDirectoryForMacros(_batchContext.Destination.Directory); - - Result = new List(); - _filePaths = ConvertObjectToStringArray(context.Source.FilePaths); - } - - private List Result { get; } - - public FileTransferResult Run(CancellationToken cancellationToken) - { - var userResultMessage = ""; - try - { - // Fetch source file info and check if files were returned. - var (files, success) = GetSourceFiles(); - - // If source directory doesn't exist, modify userResultMessage accordingly. - if (!success) - { - userResultMessage = $"Directory '{_sourceDirectoryWithMacrosExpanded}' doesn't exist."; - _logger.NotifyError(_batchContext, userResultMessage, new Exception(userResultMessage)); - return FormFailedFileTransferResult(userResultMessage); - } - - if (files == null || !files.Any()) - { - if (files == null) - _logger.NotifyInformation(_batchContext, - "Source end point returned null list for file list. If there are no files to transfer, the result should be an empty list."); - - var noSourceResult = NoSourceOperation(_batchContext, _batchContext.Source); - Result.Add(noSourceResult); - } - else - { - using (var client = CreateFtpClient(_batchContext.Connection)) - { - client.Connect(); - - if (!client.IsConnected) - { - _logger.NotifyError(_batchContext, "Error while connecting to destination: ", new Exception(userResultMessage)); - return FormFailedFileTransferResult(userResultMessage); - } - - // Check does the destination directory exists. - if (!client.DirectoryExists(_destinationDirectoryWithMacrosExpanded)) - { - if (_batchContext.Options.CreateDestinationDirectories) - { - try - { - CreateAllDirectories(client, _destinationDirectoryWithMacrosExpanded); - } - catch (Exception ex) - { - userResultMessage = $"Error while creating destination directory '{_destinationDirectoryWithMacrosExpanded}': {ex.Message}"; - _logger.NotifyError(_batchContext, userResultMessage, new Exception(userResultMessage)); - return FormFailedFileTransferResult(userResultMessage); - } - } - else - { - userResultMessage = $"Destination directory '{_destinationDirectoryWithMacrosExpanded}' was not found."; - _logger.NotifyError(_batchContext, userResultMessage, new Exception(userResultMessage)); - return FormFailedFileTransferResult(userResultMessage); - } - } - - client.SetWorkingDirectory(_destinationDirectoryWithMacrosExpanded); - - //_batchContext.DestinationFiles = client.GetListing("."); - - foreach (var file in files) - { - // Check that the connection is alive and if not try to connect again - if (!client.IsConnected) - { - client.Connect(); - _logger.NotifyInformation(_batchContext, "Reconnected."); - } - - cancellationToken.ThrowIfCancellationRequested(); - var singleTransfer = new SingleFileTransfer(file, _batchContext, client, _renamingPolicy, _logger); - var result = singleTransfer.TransferSingleFile(); - Result.Add(result); - } - client.Disconnect(); - } - } - } - catch (SocketException) - { - userResultMessage = $"Unable to establish the socket: No such host is known."; - _logger.NotifyError(_batchContext, userResultMessage, new Exception(userResultMessage)); - return FormFailedFileTransferResult(userResultMessage); - } - - return FileTransporter.FormResultFromSingleTransferResults(Result); - } - - #region Helper methods - private static FtpClient CreateFtpClient(Connection connect) - { - var client = new FtpClient(connect.Address, connect.Port, connect.UserName, connect.Password); - switch (connect.SslMode) - { - case FtpsSslMode.None: - client.EncryptionMode = FtpEncryptionMode.None; - break; - case FtpsSslMode.Implicit: - client.EncryptionMode = FtpEncryptionMode.Implicit; - break; - case FtpsSslMode.Explicit: - client.EncryptionMode = FtpEncryptionMode.Explicit; - break; - case FtpsSslMode.Auto: - client.EncryptionMode = FtpEncryptionMode.Auto; - break; - default: - throw new ArgumentOutOfRangeException(); - } - - if (connect.UseFTPS) - { - if (connect.EnableClientAuth) - client.ClientCertificates.Add(new X509Certificate2(connect.ClientCertificatePath)); - - client.ValidateCertificate += (control, e) => - { - // If cert is valid and such - go on and accept - if (e.PolicyErrors == SslPolicyErrors.None) - { - e.Accept = true; - return; - } - - // Accept if we want to accept a certain hash - e.Accept = e.Certificate.GetCertHashString() == connect.CertificateHashStringSHA1; - }; - - client.ValidateAnyCertificate = connect.ValidateAnyCertificate; - client.DataConnectionEncryption = connect.SecureDataChannel; - } - - client.NoopInterval = connect.KeepConnectionAliveInterval; - - if (!string.IsNullOrWhiteSpace(connect.Encoding)) client.Encoding = Encoding.GetEncoding(connect.Encoding); - - // Client lib timeout is in milliseconds, ours is in seconds, thus *1000 conversion - client.ConnectTimeout = connect.ConnectionTimeout * 1000; - client.LocalFileBufferSize = connect.BufferSize; - - // Transport type Binary / ASCII - switch (connect.TransportType) - { - case FtpTransportType.Binary: - client.UploadDataType = FtpDataType.Binary; - client.DownloadDataType = FtpDataType.Binary; - break; - case FtpTransportType.Ascii: - client.UploadDataType = FtpDataType.ASCII; - client.DownloadDataType = FtpDataType.ASCII; - break; - default: - throw new ArgumentOutOfRangeException($"Unknown FTP transport type {connect.TransportType}"); - } - - // Active/passive - switch (connect.Mode) - { - case FtpMode.Active: - client.DataConnectionType = FtpDataConnectionType.AutoActive; - break; - case FtpMode.Passive: - client.DataConnectionType = FtpDataConnectionType.AutoPassive; - break; - default: - throw new ArgumentOutOfRangeException($"Unknown FTP mode {connect.Mode}"); - } - - return client; - } - - private Tuple, bool> GetSourceFiles() - { - var fileItems = new List(); - - if (_filePaths != null) - { - fileItems = _filePaths.Select(p => new FileItem(p) { Name = p }).ToList(); - if (fileItems.Any()) - return new Tuple, bool>(fileItems, true); - return new Tuple, bool>(fileItems, false); - } - - // Return empty list if source directory doesn't exists. - if (!Directory.Exists(_sourceDirectoryWithMacrosExpanded)) - return new Tuple, bool>(fileItems, false); - - // fetch all file names in given directory - var files = Directory.GetFiles(_sourceDirectoryWithMacrosExpanded); - - // return Tuple with empty list and success.true if files are not found. - if (!files.Any()) - return new Tuple, bool>(fileItems, true); - - // create List of FileItems from found files. - foreach (var file in files) - { - if (Util.FileMatchesMask(Path.GetFileName(file), _batchContext.Source.FileName)) - { - FileItem item = new FileItem(Path.GetFullPath(file)); - _logger.NotifyInformation(_batchContext, $"FILE LIST {item.FullPath}"); - fileItems.Add(item); - } - } - - return new Tuple, bool>(fileItems, true); - } - - private static void CreateAllDirectories(FtpClient client, string path) - { - // Consistent forward slashes - path = path.Replace(@"\", "/"); - foreach (string dir in path.Split('/')) - { - // Ignoring leading/ending/multiple slashes - if (!string.IsNullOrWhiteSpace(dir)) - { - if (!client.DirectoryExists(dir)) - client.CreateDirectory(dir); - - client.SetWorkingDirectory(dir); - } - } - // Going back to default directory - client.SetWorkingDirectory("/"); - } - - private static string[] ConvertObjectToStringArray(object objectArray) - { - var res = objectArray as object[]; - return res?.OfType().ToArray(); - } - - private static FileTransferResult FormFailedFileTransferResult(string userResultMessage) - { - return new FileTransferResult - { - ActionSkipped = true, - Success = false, - UserResultMessage = userResultMessage, - SuccessfulTransferCount = 0, - FailedTransferCount = 0, - TransferredFileNames = new List(), - TransferErrors = new Dictionary>(), - TransferredFilePaths = new List(), - OperationsLog = new Dictionary() - }; - } - - private static FileTransferResult FormResultFromSingleTransferResults(List singleResults) - { - var success = singleResults.All(x => x.Success); - var actionSkipped = success && singleResults.All(x => x.ActionSkipped); - var userResultMessage = FileTransporter.GetUserResultMessage(singleResults.ToList()); - - var transferErrors = singleResults.Where(r => !r.Success).GroupBy(r => r.TransferredFile ?? "--unknown--") - .ToDictionary(rg => rg.Key, rg => (IList)rg.SelectMany(r => r.ErrorMessages).ToList()); - - var transferredFileResults = singleResults.Where(r => r.Success && !r.ActionSkipped).ToList(); - - return new FileTransferResult - { - ActionSkipped = actionSkipped, - Success = success, - UserResultMessage = userResultMessage, - SuccessfulTransferCount = singleResults.Count(s => s.Success && !s.ActionSkipped), - FailedTransferCount = singleResults.Count(s => !s.Success && !s.ActionSkipped), - TransferredFileNames = transferredFileResults.Select(r => r.TransferredFile ?? "--unknown--").ToList(), - TransferErrors = transferErrors, - TransferredFilePaths = transferredFileResults.Select(r => r.TransferredFilePath ?? "--unknown--").ToList(), - OperationsLog = new Dictionary() - }; - } - - private static string GetUserResultMessage(IList results) - { - var userResultMessage = string.Empty; - - var errorMessages = results.SelectMany(x => x.ErrorMessages).ToList(); - if (errorMessages.Any()) - userResultMessage = MessageJoin(userResultMessage, - $"{errorMessages.Count} Errors: {string.Join(", ", errorMessages)}"); - - var transferredFiles = results.Select(x => x.TransferredFile).Where(x => x != null).ToList(); - if (transferredFiles.Any()) - userResultMessage = MessageJoin(userResultMessage, - string.Format("{0} files transferred: {1}", transferredFiles.Count, - string.Join(", ", transferredFiles))); - else - userResultMessage = MessageJoin(userResultMessage, "No files transferred."); - - return userResultMessage; - } - - private static string MessageJoin(params string[] args) - { - return string.Join(" ", args.Where(s => !string.IsNullOrWhiteSpace(s))); - } - - private SingleFileTransferResult NoSourceOperation(BatchContext context, Source source) - { - var transferName = context.Info.TransferName ?? string.Empty; - - var msg = context.Source.FilePaths == null - ? $"No source files found from directory '{_sourceDirectoryWithMacrosExpanded}' with file mask '{source.FileName}' for transfer '{transferName}'" - : $"No source files found from FilePaths '{string.Join(", ", context.Source.FilePaths)}' for transfer '{transferName}'"; - - switch (_batchContext.Source.NotFoundAction) - { - case SourceNotFoundAction.Error: - _logger.NotifyError(context, msg, new FileNotFoundException()); - return new SingleFileTransferResult { Success = false, ErrorMessages = { msg } }; - case SourceNotFoundAction.Info: - _logger.NotifyInformation(context, msg); - return new SingleFileTransferResult { Success = true, ActionSkipped = true, ErrorMessages = { msg } }; - case SourceNotFoundAction.Ignore: - return new SingleFileTransferResult { Success = true, ActionSkipped = true, ErrorMessages = { msg } }; - default: - throw new Exception("Unknown operation in NoSourceOperation"); - } - } - #endregion - } +using FluentFTP; +using Frends.FTP.UploadFiles.Enums; +using Frends.FTP.UploadFiles.Logging; +using Frends.FTP.UploadFiles.TaskConfiguration; +using Frends.FTP.UploadFiles.TaskResult; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; + +namespace Frends.FTP.UploadFiles.Definitions +{ + /// + /// Main class for FTP file transfers + /// + internal class FileTransporter + { + private readonly IFtpLogger _logger; + private readonly BatchContext _batchContext; + private readonly string[] _filePaths; + private readonly RenamingPolicy _renamingPolicy; + private readonly string _sourceDirectoryWithMacrosExpanded; + private readonly string _destinationDirectoryWithMacrosExpanded; + + public FileTransporter(IFtpLogger logger, BatchContext context, Guid instanceId) + { + _logger = logger; + _batchContext = context; + _renamingPolicy = new RenamingPolicy(_batchContext.Info.TransferName, instanceId); + + _sourceDirectoryWithMacrosExpanded = + _renamingPolicy.ExpandDirectoryForMacros(_batchContext.Source.Directory); + _destinationDirectoryWithMacrosExpanded = + _renamingPolicy.ExpandDirectoryForMacros(_batchContext.Destination.Directory); + + Result = new List(); + _filePaths = ConvertObjectToStringArray(context.Source.FilePaths); + } + + private List Result { get; } + + public FileTransferResult Run(CancellationToken cancellationToken) + { + var userResultMessage = ""; + try + { + // Fetch source file info and check if files were returned. + var (files, success) = GetSourceFiles(); + + // If source directory doesn't exist, modify userResultMessage accordingly. + if (!success) + { + userResultMessage = $"Directory '{_sourceDirectoryWithMacrosExpanded}' doesn't exist."; + _logger.NotifyError(_batchContext, userResultMessage, new Exception(userResultMessage)); + return FormFailedFileTransferResult(userResultMessage); + } + + if (files == null || !files.Any()) + { + if (files == null) + _logger.NotifyInformation(_batchContext, + "Source end point returned null list for file list. If there are no files to transfer, the result should be an empty list."); + + var noSourceResult = NoSourceOperation(_batchContext, _batchContext.Source); + Result.Add(noSourceResult); + } + else + { + using (var client = CreateFtpClient(_batchContext.Connection)) + { + client.Connect(); + + if (!client.IsConnected) + { + _logger.NotifyError(_batchContext, "Error while connecting to destination: ", new Exception(userResultMessage)); + return FormFailedFileTransferResult(userResultMessage); + } + + // Check does the destination directory exists. + if (!client.DirectoryExists(_destinationDirectoryWithMacrosExpanded)) + { + if (_batchContext.Options.CreateDestinationDirectories) + { + try + { + CreateAllDirectories(client, _destinationDirectoryWithMacrosExpanded); + } + catch (Exception ex) + { + userResultMessage = $"Error while creating destination directory '{_destinationDirectoryWithMacrosExpanded}': {ex.Message}"; + _logger.NotifyError(_batchContext, userResultMessage, new Exception(userResultMessage)); + return FormFailedFileTransferResult(userResultMessage); + } + } + else + { + userResultMessage = $"Destination directory '{_destinationDirectoryWithMacrosExpanded}' was not found."; + _logger.NotifyError(_batchContext, userResultMessage, new Exception(userResultMessage)); + return FormFailedFileTransferResult(userResultMessage); + } + } + + client.SetWorkingDirectory(_destinationDirectoryWithMacrosExpanded); + + foreach (var file in files) + { + // Check that the connection is alive and if not try to connect again + if (!client.IsConnected) + { + client.Connect(); + _logger.NotifyInformation(_batchContext, "Reconnected."); + } + + cancellationToken.ThrowIfCancellationRequested(); + var singleTransfer = new SingleFileTransfer(file, _batchContext, client, _renamingPolicy, _logger); + var result = singleTransfer.TransferSingleFile(); + Result.Add(result); + } + client.Disconnect(); + } + } + } + catch (SocketException) + { + userResultMessage = $"Unable to establish the socket: No such host is known."; + _logger.NotifyError(_batchContext, userResultMessage, new Exception(userResultMessage)); + return FormFailedFileTransferResult(userResultMessage); + } + + return FileTransporter.FormResultFromSingleTransferResults(Result); + } + + #region Helper methods + private static FtpClient CreateFtpClient(Connection connect) + { + var client = new FtpClient(connect.Address, connect.Port, connect.UserName, connect.Password); + switch (connect.SslMode) + { + case FtpsSslMode.None: + client.EncryptionMode = FtpEncryptionMode.None; + break; + case FtpsSslMode.Implicit: + client.EncryptionMode = FtpEncryptionMode.Implicit; + break; + case FtpsSslMode.Explicit: + client.EncryptionMode = FtpEncryptionMode.Explicit; + break; + case FtpsSslMode.Auto: + client.EncryptionMode = FtpEncryptionMode.Auto; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (connect.UseFTPS) + { + if (connect.EnableClientAuth) + { + if (!string.IsNullOrEmpty(connect.ClientCertificatePath)) + { + client.ClientCertificates.Add(new X509Certificate2(connect.ClientCertificatePath)); + } + else + { + using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + { + try + { + store.Open(OpenFlags.ReadOnly); + if (!string.IsNullOrEmpty(connect.ClientCertificateName)) + client.ClientCertificates.Add(store.Certificates.Find(X509FindType.FindBySubjectName, connect.ClientCertificateName, false)[0]); + else if (!string.IsNullOrEmpty(connect.ClientCertificateThumbprint)) + client.ClientCertificates.Add(store.Certificates.Find(X509FindType.FindByThumbprint, connect.ClientCertificateThumbprint, false)[0]); + else + client.ClientCertificates.AddRange(store.Certificates); + } + finally + { + store.Close(); + } + } + } + } + + client.ValidateCertificate += (control, e) => + { + // If cert is valid and such - go on and accept + if (e.PolicyErrors == SslPolicyErrors.None) + { + e.Accept = true; + return; + } + + // Accept if we want to accept a certain hash + e.Accept = e.Certificate.GetCertHashString() == connect.CertificateHashStringSHA1; + }; + + client.ValidateAnyCertificate = connect.ValidateAnyCertificate; + client.DataConnectionEncryption = connect.SecureDataChannel; + } + + client.NoopInterval = connect.KeepConnectionAliveInterval; + + if (!string.IsNullOrWhiteSpace(connect.Encoding)) client.Encoding = Encoding.GetEncoding(connect.Encoding); + + // Client lib timeout is in milliseconds, ours is in seconds, thus *1000 conversion + client.ConnectTimeout = connect.ConnectionTimeout * 1000; + client.LocalFileBufferSize = connect.BufferSize; + + // Transport type Binary / ASCII + switch (connect.TransportType) + { + case FtpTransportType.Binary: + client.UploadDataType = FtpDataType.Binary; + client.DownloadDataType = FtpDataType.Binary; + break; + case FtpTransportType.Ascii: + client.UploadDataType = FtpDataType.ASCII; + client.DownloadDataType = FtpDataType.ASCII; + break; + default: + throw new ArgumentOutOfRangeException($"Unknown FTP transport type {connect.TransportType}"); + } + + // Active/passive + switch (connect.Mode) + { + case FtpMode.Active: + client.DataConnectionType = FtpDataConnectionType.AutoActive; + break; + case FtpMode.Passive: + client.DataConnectionType = FtpDataConnectionType.AutoPassive; + break; + default: + throw new ArgumentOutOfRangeException($"Unknown FTP mode {connect.Mode}"); + } + + return client; + } + + private Tuple, bool> GetSourceFiles() + { + var fileItems = new List(); + + if (_filePaths != null) + { + fileItems = _filePaths.Select(p => new FileItem(p) { Name = p }).ToList(); + if (fileItems.Any()) + return new Tuple, bool>(fileItems, true); + return new Tuple, bool>(fileItems, false); + } + + // Return empty list if source directory doesn't exists. + if (!Directory.Exists(_sourceDirectoryWithMacrosExpanded)) + return new Tuple, bool>(fileItems, false); + + // fetch all file names in given directory + var files = Directory.GetFiles(_sourceDirectoryWithMacrosExpanded); + + // return Tuple with empty list and success.true if files are not found. + if (!files.Any()) + return new Tuple, bool>(fileItems, true); + + // create List of FileItems from found files. + foreach (var file in files) + { + if (Util.FileMatchesMask(Path.GetFileName(file), _batchContext.Source.FileName)) + { + FileItem item = new FileItem(Path.GetFullPath(file)); + _logger.NotifyInformation(_batchContext, $"FILE LIST {item.FullPath}"); + fileItems.Add(item); + } + } + + return new Tuple, bool>(fileItems, true); + } + + private static void CreateAllDirectories(FtpClient client, string path) + { + // Consistent forward slashes + path = path.Replace(@"\", "/"); + foreach (string dir in path.Split('/')) + { + // Ignoring leading/ending/multiple slashes + if (!string.IsNullOrWhiteSpace(dir)) + { + if (!client.DirectoryExists(dir)) + client.CreateDirectory(dir); + + client.SetWorkingDirectory(dir); + } + } + // Going back to default directory + client.SetWorkingDirectory("/"); + } + + private static string[] ConvertObjectToStringArray(object objectArray) + { + var res = objectArray as object[]; + return res?.OfType().ToArray(); + } + + private static FileTransferResult FormFailedFileTransferResult(string userResultMessage) + { + return new FileTransferResult + { + ActionSkipped = true, + Success = false, + UserResultMessage = userResultMessage, + SuccessfulTransferCount = 0, + FailedTransferCount = 0, + TransferredFileNames = new List(), + TransferErrors = new Dictionary>(), + TransferredFilePaths = new List(), + OperationsLog = new Dictionary() + }; + } + + private static FileTransferResult FormResultFromSingleTransferResults(List singleResults) + { + var success = singleResults.All(x => x.Success); + var actionSkipped = success && singleResults.All(x => x.ActionSkipped); + var userResultMessage = FileTransporter.GetUserResultMessage(singleResults.ToList()); + + var transferErrors = singleResults.Where(r => !r.Success).GroupBy(r => r.TransferredFile ?? "--unknown--") + .ToDictionary(rg => rg.Key, rg => (IList)rg.SelectMany(r => r.ErrorMessages).ToList()); + + var transferredFileResults = singleResults.Where(r => r.Success && !r.ActionSkipped).ToList(); + + return new FileTransferResult + { + ActionSkipped = actionSkipped, + Success = success, + UserResultMessage = userResultMessage, + SuccessfulTransferCount = singleResults.Count(s => s.Success && !s.ActionSkipped), + FailedTransferCount = singleResults.Count(s => !s.Success && !s.ActionSkipped), + TransferredFileNames = transferredFileResults.Select(r => r.TransferredFile ?? "--unknown--").ToList(), + TransferErrors = transferErrors, + TransferredFilePaths = transferredFileResults.Select(r => r.TransferredFilePath ?? "--unknown--").ToList(), + OperationsLog = new Dictionary() + }; + } + + private static string GetUserResultMessage(IList results) + { + var userResultMessage = string.Empty; + + var errorMessages = results.SelectMany(x => x.ErrorMessages).ToList(); + if (errorMessages.Any()) + userResultMessage = MessageJoin(userResultMessage, + $"{errorMessages.Count} Errors: {string.Join(", ", errorMessages)}"); + + var transferredFiles = results.Select(x => x.TransferredFile).Where(x => x != null).ToList(); + if (transferredFiles.Any()) + userResultMessage = MessageJoin(userResultMessage, + string.Format("{0} files transferred: {1}", transferredFiles.Count, + string.Join(", ", transferredFiles))); + else + userResultMessage = MessageJoin(userResultMessage, "No files transferred."); + + return userResultMessage; + } + + private static string MessageJoin(params string[] args) + { + return string.Join(" ", args.Where(s => !string.IsNullOrWhiteSpace(s))); + } + + private SingleFileTransferResult NoSourceOperation(BatchContext context, Source source) + { + var transferName = context.Info.TransferName ?? string.Empty; + + var msg = context.Source.FilePaths == null + ? $"No source files found from directory '{_sourceDirectoryWithMacrosExpanded}' with file mask '{source.FileName}' for transfer '{transferName}'" + : $"No source files found from FilePaths '{string.Join(", ", context.Source.FilePaths)}' for transfer '{transferName}'"; + + switch (_batchContext.Source.NotFoundAction) + { + case SourceNotFoundAction.Error: + _logger.NotifyError(context, msg, new FileNotFoundException()); + return new SingleFileTransferResult { Success = false, ErrorMessages = { msg } }; + case SourceNotFoundAction.Info: + _logger.NotifyInformation(context, msg); + return new SingleFileTransferResult { Success = true, ActionSkipped = true, ErrorMessages = { msg } }; + case SourceNotFoundAction.Ignore: + return new SingleFileTransferResult { Success = true, ActionSkipped = true, ErrorMessages = { msg } }; + default: + throw new Exception("Unknown operation in NoSourceOperation"); + } + } + #endregion + } } \ No newline at end of file diff --git a/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles.csproj b/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles.csproj index 6600a5f..8a83024 100644 --- a/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles.csproj +++ b/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles.csproj @@ -9,7 +9,7 @@ true Frends true - 1.0.2 + 1.1.0 Task for uploading files to FTP(S) servers. diff --git a/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles/TaskConfiguration/Connection.cs b/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles/TaskConfiguration/Connection.cs index e04bbcd..aecc5b5 100644 --- a/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles/TaskConfiguration/Connection.cs +++ b/Frends.FTP.UploadFiles/Frends.FTP.UploadFiles/TaskConfiguration/Connection.cs @@ -146,6 +146,22 @@ public class Connection [UIHint(nameof(UseFTPS), "", true)] public bool EnableClientAuth { get; set; } + /// + /// Optional. Enables certification search by name from the certification store of current user. + /// + /// mycert.crt + [DefaultValue("")] + [UIHint(nameof(EnableClientAuth), "", true)] + public string ClientCertificateName { get; set; } + + /// + /// Optional. Enables certification search by thumbprint from the certification store of current user. + /// + /// a909502dd82ae41433e6f83886b00d4277a32a7b + [DefaultValue("")] + [UIHint(nameof(EnableClientAuth), "", true)] + public string ClientCertificateThumbprint { get; set; } + /// /// If enabled the any certificate will be considered valid. ///