/// <summary> /// Initializes a new instance of the <see cref="FtpConnection"/> class. /// </summary> /// <param name="socket">The socket to use to communicate with the client.</param> /// <param name="logger">The logger for the FTP connection.</param> /// <param name="options">The options for the FTP connection.</param> /// <param name="serviceProvider">The service provider used to query services.</param> public FtpConnection( [NotNull] TcpClient socket, [NotNull] IOptions <FtpConnectionOptions> options, [NotNull] IServiceProvider serviceProvider, [CanBeNull] ILogger <IFtpConnection> logger = null) { var endpoint = (IPEndPoint)socket.Client.RemoteEndPoint; RemoteAddress = new Address(endpoint.Address.ToString(), endpoint.Port); var properties = new Dictionary <string, object> { ["RemoteAddress"] = RemoteAddress.ToString(true), ["RemoteIp"] = RemoteAddress.IPAddress?.ToString(), ["RemotePort"] = RemoteAddress.Port, }; _loggerScope = logger?.BeginScope(properties); _socket = socket; Log = logger; SocketStream = OriginalStream = socket.GetStream(); Encoding = options.Value.DefaultEncoding ?? Encoding.ASCII; Data = new FtpConnectionData(new BackgroundCommandHandler(this)); // Lazy is required, because we need access to the FTP connection in the command handler constructor _commandHandlersDict = new Lazy <IReadOnlyDictionary <string, IFtpCommandHandler> >( () => { var commandHandlers = serviceProvider.GetRequiredService <IEnumerable <IFtpCommandHandler> >().ToList(); var dict = commandHandlers .SelectMany(x => x.Names, (item, name) => new { Name = name, Item = item }) .ToLookup(x => x.Name, x => x.Item, StringComparer.OrdinalIgnoreCase) .ToDictionary(x => x.Key, x => x.Last()); var extensions = serviceProvider.GetRequiredService <IEnumerable <IFtpCommandHandlerExtension> >().ToList(); foreach (var handler in commandHandlers) { extensions.AddRange(handler.GetExtensions()); } // Register extensions with commands foreach (var extension in extensions) { if (dict.TryGetValue(extension.ExtensionFor, out var handler)) { if (handler is IFtpCommandHandlerExtensionHost extensionHost) { foreach (var name in extension.Names) { extensionHost.Extensions.Add(name, extension); } } } } return(dict); }); }
/// <summary> /// Initializes a new instance of the <see cref="FtpConnection"/> class. /// </summary> /// <param name="server">The server this connection belongs to</param> /// <param name="socket">The socket to use to communicate with the client</param> /// <param name="encoding">The encoding to use for the LIST/NLST commands</param> public FtpConnection([NotNull] FtpServer server, [NotNull] ITcpSocketClient socket, [NotNull] Encoding encoding) { Server = server; _socket = socket; RemoteAddress = new Address(socket.RemoteAddress, socket.RemotePort); SocketStream = OriginalStream = socket.GetStream(); Encoding = encoding; Data = new FtpConnectionData(this); var commandHandlers = Server.CommandsHandlerFactory.CreateCommandHandlers(this).ToList(); CommandHandlers = commandHandlers .SelectMany(x => x.Names, (item, name) => new { Name = name, Item = item }) .ToDictionary(x => x.Name, x => x.Item, StringComparer.OrdinalIgnoreCase); // Add stand-alone extensions AddExtensions(Server.CommandsHandlerFactory.CreateCommandHandlerExtensions(this)); // Add extensions provided by command handlers foreach (var commandHandler in commandHandlers) AddExtensions(commandHandler.GetExtensions()); }
/// <summary> /// Initializes a new instance of the <see cref="FtpConnection"/> class. /// </summary> /// <param name="socket">The socket to use to communicate with the client.</param> /// <param name="connectionAccessor">The accessor to get the connection that is active during the <see cref="FtpCommandHandler.Process"/> method execution.</param> /// <param name="commandHandlerExtensions">The registered command handler extensions.</param> /// <param name="logger">The logger for the FTP connection.</param> /// <param name="options">The options for the FTP connection.</param> /// <param name="commandHandlers">The registered command handlers.</param> public FtpConnection( [NotNull] TcpClient socket, [NotNull] IOptions <FtpConnectionOptions> options, [NotNull] IFtpConnectionAccessor connectionAccessor, [NotNull, ItemNotNull] IEnumerable <IFtpCommandHandler> commandHandlers, [NotNull, ItemNotNull] IEnumerable <IFtpCommandHandlerExtension> commandHandlerExtensions, [CanBeNull] ILogger <IFtpConnection> logger = null) { var endpoint = (IPEndPoint)socket.Client.RemoteEndPoint; RemoteAddress = new Address(endpoint.Address.ToString(), endpoint.Port); var properties = new Dictionary <string, object> { ["RemoteAddress"] = RemoteAddress.ToString(true), ["RemoteIp"] = RemoteAddress.IPAddress?.ToString(), ["RemotePort"] = RemoteAddress.Port, }; _loggerScope = logger?.BeginScope(properties); _socket = socket; _connectionAccessor = connectionAccessor; Log = logger; SocketStream = OriginalStream = socket.GetStream(); Encoding = options.Value.DefaultEncoding ?? Encoding.ASCII; PromiscuousPasv = options.Value.PromiscuousPasv; Data = new FtpConnectionData(new BackgroundCommandHandler(this)); var commandHandlersList = commandHandlers.ToList(); var dict = commandHandlersList .SelectMany(x => x.Names, (item, name) => new { Name = name, Item = item }) .ToLookup(x => x.Name, x => x.Item, StringComparer.OrdinalIgnoreCase) .ToDictionary(x => x.Key, x => x.Last()); _extensions = commandHandlerExtensions.ToList(); CommandHandlers = dict; }
/// <summary> /// Initializes a new instance of the <see cref="FtpConnection"/> class. /// </summary> /// <param name="server">The server this connection belongs to</param> /// <param name="socket">The socket to use to communicate with the client</param> /// <param name="encoding">The encoding to use for the LIST/NLST commands</param> public FtpConnection([NotNull] FtpServer server, [NotNull] ITcpSocketClient socket, [NotNull] Encoding encoding) { Server = server; _socket = socket; RemoteAddress = new Address(socket.RemoteAddress, socket.RemotePort); SocketStream = OriginalStream = socket.GetStream(); Encoding = encoding; Data = new FtpConnectionData(this); var commandHandlers = Server.CommandsHandlerFactory.CreateCommandHandlers(this).ToList(); CommandHandlers = commandHandlers .SelectMany(x => x.Names, (item, name) => new { Name = name, Item = item }) .ToDictionary(x => x.Name, x => x.Item, StringComparer.OrdinalIgnoreCase); // Add stand-alone extensions AddExtensions(Server.CommandsHandlerFactory.CreateCommandHandlerExtensions(this)); // Add extensions provided by command handlers foreach (var commandHandler in commandHandlers) { AddExtensions(commandHandler.GetExtensions()); } }
/// <summary> /// Initializes a new instance of the <see cref="FtpConnection"/> class. /// </summary> /// <param name="socketAccessor">The accessor to get the socket used to communicate with the client.</param> /// <param name="options">The options for the FTP connection.</param> /// <param name="portOptions">The <c>PORT</c> command options.</param> /// <param name="connectionAccessor">The accessor to get the connection that is active during the <see cref="FtpCommandHandler.Process"/> method execution.</param> /// <param name="catalogLoader">The catalog loader for the FTP server.</param> /// <param name="serverCommandExecutor">The executor for server commands.</param> /// <param name="serviceProvider">The service provider for the connection.</param> /// <param name="secureDataConnectionWrapper">Wraps a data connection into an SSL stream.</param> /// <param name="sslStreamWrapperFactory">The SSL stream wrapper factory.</param> /// <param name="logger">The logger for the FTP connection.</param> public FtpConnection( TcpSocketClientAccessor socketAccessor, IOptions <FtpConnectionOptions> options, IOptions <PortCommandOptions> portOptions, IFtpConnectionAccessor connectionAccessor, IFtpCatalogLoader catalogLoader, IServerCommandExecutor serverCommandExecutor, IServiceProvider serviceProvider, SecureDataConnectionWrapper secureDataConnectionWrapper, ISslStreamWrapperFactory sslStreamWrapperFactory, ILogger <FtpConnection>?logger = null) { var socket = socketAccessor.TcpSocketClient ?? throw new InvalidOperationException("The socket to communicate with the client was not set"); ConnectionServices = serviceProvider; ConnectionId = "FTP-" + Guid.NewGuid().ToString("N"); _dataPort = portOptions.Value.DataPort; var remoteEndPoint = _remoteEndPoint = (IPEndPoint)socket.Client.RemoteEndPoint; #pragma warning disable 618 RemoteAddress = new Address(remoteEndPoint.Address.ToString(), remoteEndPoint.Port); #pragma warning restore 618 var properties = new Dictionary <string, object?> { ["RemoteAddress"] = remoteEndPoint.ToString(), ["RemoteIp"] = remoteEndPoint.Address.ToString(), ["RemotePort"] = remoteEndPoint.Port, ["ConnectionId"] = ConnectionId, }; _loggerScope = logger?.BeginScope(properties); _socket = socket; _connectionAccessor = connectionAccessor; _serverCommandExecutor = serverCommandExecutor; _secureDataConnectionWrapper = secureDataConnectionWrapper; _serverCommandChannel = Channel.CreateBounded <IServerCommand>(new BoundedChannelOptions(3)); _logger = logger; var parentFeatures = new FeatureCollection(); var connectionFeature = new ConnectionFeature( (IPEndPoint)socket.Client.LocalEndPoint, remoteEndPoint); var secureConnectionFeature = new SecureConnectionFeature(); var applicationInputPipe = new Pipe(); var applicationOutputPipe = new Pipe(); var socketPipe = new DuplexPipe(_socketCommandPipe.Reader, _socketResponsePipe.Writer); var connectionPipe = new DuplexPipe(applicationOutputPipe.Reader, applicationInputPipe.Writer); var originalStream = socketAccessor.TcpSocketStream ?? socket.GetStream(); _streamReaderService = new ConnectionClosingNetworkStreamReader( originalStream, _socketCommandPipe.Writer, _cancellationTokenSource); _streamWriterService = new StreamPipeWriterService( originalStream, _socketResponsePipe.Reader, _cancellationTokenSource.Token); _networkStreamFeature = new NetworkStreamFeature( new SecureConnectionAdapter( socketPipe, connectionPipe, sslStreamWrapperFactory, _cancellationTokenSource.Token), applicationOutputPipe.Writer); parentFeatures.Set <IConnectionFeature>(connectionFeature); parentFeatures.Set <ISecureConnectionFeature>(secureConnectionFeature); parentFeatures.Set <IServerCommandFeature>(new ServerCommandFeature(_serverCommandChannel)); parentFeatures.Set <INetworkStreamFeature>(_networkStreamFeature); var features = new FeatureCollection(parentFeatures); #pragma warning disable 618 Data = new FtpConnectionData( options.Value.DefaultEncoding ?? Encoding.ASCII, features, catalogLoader); #pragma warning restore 618 Features = features; _commandReader = ReadCommandsFromPipeline( applicationInputPipe.Reader, _ftpCommandChannel.Writer, _cancellationTokenSource.Token); }
/// <summary> /// Initializes a new instance of the <see cref="FtpConnection"/> class. /// </summary> /// <param name="socket">The socket to use to communicate with the client.</param> /// <param name="options">The options for the FTP connection.</param> /// <param name="portOptions">The <c>PORT</c> command options.</param> /// <param name="connectionAccessor">The accessor to get the connection that is active during the <see cref="FtpCommandHandler.Process"/> method execution.</param> /// <param name="catalogLoader">The catalog loader for the FTP server.</param> /// <param name="serverCommandExecutor">The executor for server commands.</param> /// <param name="serviceProvider">The service provider for the connection.</param> /// <param name="secureDataConnectionWrapper">Wraps a data connection into an SSL stream.</param> /// <param name="serverMessages">The server messages.</param> /// <param name="sslStreamWrapperFactory">The SSL stream wrapper factory.</param> /// <param name="logger">The logger for the FTP connection.</param> public FtpConnection( [NotNull] TcpClient socket, [NotNull] IOptions <FtpConnectionOptions> options, [NotNull] IOptions <PortCommandOptions> portOptions, [NotNull] IFtpConnectionAccessor connectionAccessor, [NotNull] IFtpCatalogLoader catalogLoader, [NotNull] IServerCommandExecutor serverCommandExecutor, [NotNull] IServiceProvider serviceProvider, [NotNull] SecureDataConnectionWrapper secureDataConnectionWrapper, [NotNull] IFtpServerMessages serverMessages, [NotNull] ISslStreamWrapperFactory sslStreamWrapperFactory, [CanBeNull] ILogger <FtpConnection> logger = null) { ConnectionServices = serviceProvider; ConnectionId = "FTP-" + Guid.NewGuid().ToString("N"); _dataPort = portOptions.Value.DataPort; var endpoint = (IPEndPoint)socket.Client.RemoteEndPoint; var remoteAddress = _remoteAddress = new Address(endpoint.Address.ToString(), endpoint.Port); var properties = new Dictionary <string, object> { ["RemoteAddress"] = remoteAddress.ToString(true), ["RemoteIp"] = remoteAddress.IPAddress?.ToString(), ["RemotePort"] = remoteAddress.Port, ["ConnectionId"] = ConnectionId, }; _loggerScope = logger?.BeginScope(properties); _socket = socket; _connectionAccessor = connectionAccessor; _serverCommandExecutor = serverCommandExecutor; _secureDataConnectionWrapper = secureDataConnectionWrapper; _serverMessages = serverMessages; _serverCommandChannel = Channel.CreateBounded <IServerCommand>(new BoundedChannelOptions(3)); _logger = logger; var parentFeatures = new FeatureCollection(); var connectionFeature = new ConnectionFeature( (IPEndPoint)socket.Client.LocalEndPoint, remoteAddress); var secureConnectionFeature = new SecureConnectionFeature(socket); var applicationInputPipe = new Pipe(); var applicationOutputPipe = new Pipe(); var socketPipe = new DuplexPipe(_socketCommandPipe.Reader, _socketResponsePipe.Writer); var connectionPipe = new DuplexPipe(applicationOutputPipe.Reader, applicationInputPipe.Writer); _networkStreamFeature = new NetworkStreamFeature( new SecureConnectionAdapter( socketPipe, connectionPipe, sslStreamWrapperFactory, _cancellationTokenSource.Token), new ConnectionClosingNetworkStreamReader( secureConnectionFeature.OriginalStream, _socketCommandPipe.Writer, _cancellationTokenSource), new StreamPipeWriterService( secureConnectionFeature.OriginalStream, _socketResponsePipe.Reader, _cancellationTokenSource.Token), applicationOutputPipe.Writer); parentFeatures.Set <IConnectionFeature>(connectionFeature); parentFeatures.Set <ISecureConnectionFeature>(secureConnectionFeature); parentFeatures.Set <IServerCommandFeature>(new ServerCommandFeature(_serverCommandChannel)); parentFeatures.Set <INetworkStreamFeature>(_networkStreamFeature); var features = new FeatureCollection(parentFeatures); #pragma warning disable 618 Data = new FtpConnectionData( options.Value.DefaultEncoding ?? Encoding.ASCII, features, catalogLoader); #pragma warning restore 618 Features = features; _commandReader = ReadCommandsFromPipeline( applicationInputPipe.Reader, _ftpCommandChannel.Writer, _cancellationTokenSource.Token); }