private Tuple <FtpCommand, IFtpCommandBase, bool> FindCommandHandler(FtpCommand command) { if (!CommandHandlers.TryGetValue(command.Name, out var handler)) { return(null); } if (!string.IsNullOrWhiteSpace(command.Argument) && handler is IFtpCommandHandlerExtensionHost extensionHost) { var extensionCommand = FtpCommand.Parse(command.Argument); if (extensionHost.Extensions.TryGetValue(extensionCommand.Name, out var extension)) { return(Tuple.Create(extensionCommand, (IFtpCommandBase)extension, extension.IsLoginRequired ?? handler.IsLoginRequired)); } } return(Tuple.Create(command, (IFtpCommandBase)handler, handler.IsLoginRequired)); }
private Tuple <FtpCommand, FtpCommandHandlerBase, bool> FindCommandHandler(FtpCommand command) { FtpCommandHandler handler; if (!CommandHandlers.TryGetValue(command.Name, out handler)) { return(null); } var extensionHost = handler as IFtpCommandHandlerExtensionHost; if (!string.IsNullOrWhiteSpace(command.Argument) && extensionHost != null) { var extensionCommand = FtpCommand.Parse(command.Argument); FtpCommandHandlerExtension extension; if (extensionHost.Extensions.TryGetValue(extensionCommand.Name, out extension)) { return(Tuple.Create(extensionCommand, (FtpCommandHandlerBase)extension, extension.IsLoginRequired ?? handler.IsLoginRequired)); } } return(Tuple.Create(command, (FtpCommandHandlerBase)handler, handler.IsLoginRequired)); }
/// <inheritdoc /> protected override Task <IFtpResponse> ExecuteCommandAsync(FtpCommand ftpCommand, CancellationToken cancellationToken = default) { switch (ftpCommand.Name.Trim().ToUpperInvariant()) { case "AUTH": return(HandleAuthAsync(ftpCommand.Argument, cancellationToken)); case "ADAT": return(HandleAdatAsync(ftpCommand.Argument, cancellationToken)); case "USER": return(HandleUserAsync(ftpCommand.Argument, cancellationToken)); case "PASS": return(HandlePassAsync(ftpCommand.Argument, cancellationToken)); case "ACCT": return(HandleAcctAsync(ftpCommand.Argument, cancellationToken)); default: return(UnhandledCommandAsync(ftpCommand, cancellationToken)); } }
/// <summary> /// Executes the given FTP command. /// </summary> /// <param name="ftpCommand">The FTP command to execute.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task returning the response.</returns> public async Task <IFtpResponse> ExecuteAsync(FtpCommand ftpCommand, CancellationToken cancellationToken = default) { var commandTransitions = _possibleTransitions .Where(x => x.IsMatch(ftpCommand.Name)) .ToList(); if (commandTransitions.Count == 0) { return(new FtpResponse(503, T("Bad sequence of commands"))); } var response = await ExecuteCommandAsync(ftpCommand, cancellationToken) .ConfigureAwait(false); if (response == null) { return(new FtpResponse(421, T("Service not available"))); } var foundStatus = commandTransitions.SingleOrDefault(x => x.IsMatch(ftpCommand.Name, response.Code)); if (foundStatus == null) { return(new FtpResponse(421, T("Service not available"))); } SetStatus(foundStatus.Target); // Ugh ... this is a hack, but I have to fix this later. if (response is FtpResponse ftpResponse && ftpResponse.Message == null) { return(null); } return(response); }
private FtpCommand CreateFtpCommand(byte[] command) { var message = Encoding.GetString(command, 0, command.Length); return(FtpCommand.Parse(message)); }
/// <summary> /// Execute the command. All status checks are already done. /// </summary> /// <param name="ftpCommand">The FTP command to execute.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task returning the response.</returns> protected abstract Task <IFtpResponse> ExecuteCommandAsync([NotNull] FtpCommand ftpCommand, CancellationToken cancellationToken = default);
/// <summary> /// Executes some code with error handling. /// </summary> /// <param name="connection">The connection to execute the code for.</param> /// <param name="command">The command to execute the code for.</param> /// <param name="commandAction">The action to be executed.</param> /// <param name="logger">The logger to be used for logging.</param> /// <param name="cancellationToken">The cancellation token to signal command abortion.</param> /// <returns>The task with the (optional) response.</returns> public static async Task <IFtpResponse?> ExecuteCommand( this IFtpConnection connection, FtpCommand command, Func <FtpCommand, CancellationToken, Task <IFtpResponse?> > commandAction, ILogger?logger, CancellationToken cancellationToken) { var localizationFeature = connection.Features.Get <ILocalizationFeature>(); IFtpResponse?response; try { response = await commandAction(command, cancellationToken) .ConfigureAwait(false); } catch (Exception ex) { var exception = ex; while (exception is AggregateException aggregateException) { exception = aggregateException.InnerException; } switch (exception) { case ValidationException validationException: response = new FtpResponse( 425, validationException.Message); logger?.LogWarning(validationException.Message); break; #if !NETSTANDARD1_3 case SocketException se when se.ErrorCode == (int)SocketError.ConnectionAborted: #endif case OperationCanceledException _: response = new FtpResponse(426, localizationFeature.Catalog.GetString("Connection closed; transfer aborted.")); logger?.LogTrace("Command {command} cancelled with response {response}", command, response); break; case FileSystemException fse: { var message = fse.Message != null ? $"{fse.FtpErrorName}: {fse.Message}" : fse.FtpErrorName; logger?.LogInformation("Rejected command ({command}) with error {code} {message}", command, fse.FtpErrorCode, message); response = new FtpResponse(fse.FtpErrorCode, message); break; } case NotSupportedException nse: { var message = nse.Message ?? localizationFeature.Catalog.GetString("Command {command} not supported", command); logger?.LogInformation(message); response = new FtpResponse(502, message); break; } default: logger?.LogError(0, ex, "Failed to process message ({command})", command); response = new FtpResponse(501, localizationFeature.Catalog.GetString("Syntax error in parameters or arguments.")); break; } } return(response); }
private async Task ProcessMessage(FtpCommand command) { FtpResponse response; Log?.Trace(command); var result = FindCommandHandler(command); if (result != null) { var handler = result.Item2; var handlerCommand = result.Item1; var isLoginRequired = result.Item3; if (isLoginRequired && !Data.IsLoggedIn) { response = new FtpResponse(530, "Not logged in."); } else { try { var cmdHandler = handler as FtpCommandHandler; var isAbortable = cmdHandler?.IsAbortable ?? false; if (isAbortable) { var newBackgroundTask = Data.BackgroundCommandHandler.Execute(handler, handlerCommand); if (newBackgroundTask != null) { _activeBackgroundTask = newBackgroundTask; response = null; } else { response = new FtpResponse(503, "Parallel commands aren't allowed."); } } else { response = await handler.Process(handlerCommand, _cancellationTokenSource.Token) .ConfigureAwait(false); } } catch (FileSystemException fse) { var message = fse.Message != null ? $"{fse.FtpErrorName}: {fse.Message}" : fse.FtpErrorName; Log?.LogInformation($"Rejected command ({command}) with error {fse.FtpErrorCode} {message}"); response = new FtpResponse(fse.FtpErrorCode, message); } catch (NotSupportedException nse) { var message = nse.Message ?? $"Command {command} not supported"; Log?.LogInformation(message); response = new FtpResponse(502, message); } catch (Exception ex) { Log?.LogError(ex, "Failed to process message ({0})", command); response = new FtpResponse(501, "Syntax error in parameters or arguments."); } } } else { response = new FtpResponse(500, "Syntax error, command unrecognized."); } if (response != null) { await WriteAsync(response, _cancellationTokenSource.Token).ConfigureAwait(false); } }
/// <summary> /// Called when the command couldn't be handled. /// </summary> /// <param name="ftpCommand">The FTP command causing the problem.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The FTP response to be returned.</returns> protected virtual Task <IFtpResponse> UnhandledCommandAsync(FtpCommand ftpCommand, CancellationToken cancellationToken) { return(Task.FromResult <IFtpResponse>(new FtpResponse(421, T("Service not available")))); }
private Tuple<FtpCommand, FtpCommandHandlerBase, bool> FindCommandHandler(FtpCommand command) { FtpCommandHandler handler; if (!CommandHandlers.TryGetValue(command.Name, out handler)) return null; var extensionHost = handler as IFtpCommandHandlerExtensionHost; if (!string.IsNullOrWhiteSpace(command.Argument) && extensionHost != null) { var extensionCommand = FtpCommand.Parse(command.Argument); FtpCommandHandlerExtension extension; if (extensionHost.Extensions.TryGetValue(extensionCommand.Name, out extension)) { return Tuple.Create(extensionCommand, (FtpCommandHandlerBase)extension, extension.IsLoginRequired ?? handler.IsLoginRequired); } } return Tuple.Create(command, (FtpCommandHandlerBase)handler, handler.IsLoginRequired); }
private async Task ProcessMessage(FtpCommand command) { FtpResponse response; Log?.Trace(command); var result = FindCommandHandler(command); if (result != null) { var handler = result.Item2; var handlerCommand = result.Item1; var isLoginRequired = result.Item3; if (isLoginRequired && !Data.IsLoggedIn) { response = new FtpResponse(530, "Not logged in."); } else { try { var cmdHandler = handler as FtpCommandHandler; var isAbortable = cmdHandler?.IsAbortable ?? false; if (isAbortable) { var newBackgroundTask = Data.BackgroundCommandHandler.Execute(handler, handlerCommand); if (newBackgroundTask != null) { _activeBackgroundTask = newBackgroundTask; response = null; } else { response = new FtpResponse(503, "Parallel commands aren't allowed."); } } else { response = await handler.Process(handlerCommand, _cancellationTokenSource.Token); } } catch (Exception ex) { Log?.Error(ex, "Failed to process message ({0})", command); response = new FtpResponse(501, "Syntax error in parameters or arguments."); } } } else { response = new FtpResponse(500, "Syntax error, command unrecognized."); } if (response != null) await WriteAsync(response, _cancellationTokenSource.Token); }
/// <summary> /// Logs a trace message with the data of the <see cref="FtpCommand"/> /// </summary> /// <param name="log">The <see cref="IFtpLog"/> to use</param> /// <param name="command">The <see cref="FtpCommand"/> to log</param> public static void Trace([NotNull] this IFtpLog log, [NotNull] FtpCommand command) { log.Trace("{0}", command); }
public abstract Task <FtpResponse> Process([NotNull] FtpCommand command, CancellationToken cancellationToken);
public Task <FtpResponse> Execute([NotNull] FtpCommandHandlerBase handler, [NotNull] FtpCommand command) { Contract.Ensures(Contract.Result <Task <FtpResponse> >() != null); lock (_syncRoot) { if (_handlerTask != null) { return(null); } _cancellationTokenSource = new CancellationTokenSource(); _handlerTask = handler.Process(command, _cancellationTokenSource.Token); } var taskCanceled = _handlerTask .ContinueWith( t => { var response = new FtpResponse(426, "Connection closed; transfer aborted."); Debug.WriteLine($"Background task cancelled with response {response}"); return(response); }, TaskContinuationOptions.OnlyOnCanceled); var taskCompleted = _handlerTask .ContinueWith( t => { var response = t.Result; Debug.WriteLine($"{DateTimeOffset.UtcNow} Background task finished successfully with response {response}"); return(response); }, TaskContinuationOptions.OnlyOnRanToCompletion); var taskFaulted = _handlerTask .ContinueWith( t => { var ex = t.Exception; _connection.Log?.Error(ex, "Error while processing background command {0}", command); var response = new FtpResponse(501, "Syntax error in parameters or arguments."); Debug.WriteLine($"Background task failed with response {response}"); return(response); }, TaskContinuationOptions.OnlyOnFaulted); taskFaulted.ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled); taskCompleted.ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled); taskCanceled.ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled); return(Task.Run( () => { var tasks = new List <Task <FtpResponse> > { taskCompleted, taskCanceled, taskFaulted }; do { try { var waitTasks = tasks.Where(x => !x.IsCompleted).Cast <Task>().ToArray(); if (waitTasks.Length != 0) { Debug.WriteLine($"Waiting for {waitTasks.Length} background tasks"); Task.WaitAll(waitTasks); } } catch (AggregateException ex) { ex.Handle(e => e is TaskCanceledException); } }while (tasks.Any(t => !t.IsCompleted)); var response = tasks.Single(x => x.Status == TaskStatus.RanToCompletion).Result; Debug.WriteLine($"{DateTimeOffset.UtcNow} Background task finished with response {response}"); lock (_syncRoot) _handlerTask = null; return response; })); }