/// <inheritdoc/> public override async Task<FtpResponse> Process(FtpCommand command, CancellationToken cancellationToken) { var argument = command.Argument; var path = Data.Path.Clone(); IUnixFileSystemEntry targetEntry; if (string.IsNullOrEmpty(argument)) { targetEntry = path.Count == 0 ? Data.FileSystem.Root : path.Peek(); } else { var foundEntry = await Data.FileSystem.SearchEntryAsync(path, argument, cancellationToken); if (foundEntry?.Entry == null) return new FtpResponse(550, "File system entry not found."); targetEntry = foundEntry.Entry; } var dirEntry = targetEntry as IUnixDirectoryEntry; var isDirEntry = dirEntry != null; var listDir = string.Equals(command.Name, "MLSD", StringComparison.OrdinalIgnoreCase); if (listDir && !isDirEntry) return new FtpResponse(501, "Not a directory."); await Connection.WriteAsync(new FtpResponse(150, "Opening data connection."), cancellationToken); ITcpSocketClient responseSocket; try { responseSocket = await Connection.CreateResponseSocket(); } catch (Exception) { return new FtpResponse(425, "Can't open data connection."); } try { var formatter = new FactsListFormatter(Data.User, Data.FileSystem, path, Data.ActiveMlstFacts); var encoding = Data.NlstEncoding ?? Connection.Encoding; using (var stream = await Connection.CreateEncryptedStream(responseSocket.WriteStream)) { using (var writer = new StreamWriter(stream, encoding, 4096, true) { NewLine = "\r\n", }) { if (listDir) { foreach (var line in formatter.GetPrefix(dirEntry)) { Connection.Log?.Debug(line); await writer.WriteLineAsync(line); } foreach (var entry in await Data.FileSystem.GetEntriesAsync(dirEntry, cancellationToken)) { var line = formatter.Format(entry); Connection.Log?.Debug(line); await writer.WriteLineAsync(line); } foreach (var line in formatter.GetSuffix(dirEntry)) { Connection.Log?.Debug(line); await writer.WriteLineAsync(line); } } else { var line = formatter.Format(targetEntry); Connection.Log?.Debug(line); await writer.WriteLineAsync(line); } await writer.FlushAsync(); } await stream.FlushAsync(cancellationToken); } } finally { responseSocket.Dispose(); } // Use 250 when the connection stays open. return new FtpResponse(226, "Closing data connection."); }