/// <summary> /// Verifies that provided password is correct for the user. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { var user = knownUsers .FirstOrDefault(u => u.Name == session.Username && u.Password == arguments); if (user == null) { session.Logger.WriteWarning( TraceResources.InvalidLoginAttemptFormat, session.Username, arguments); return(FtpResponsesAsync.NotLoggedIn); } session.FileSystem = user.FileSystem; session.CurrentDirectory = new VirtualPath(); session.Logger.WriteInfo(TraceResources.UserLoggedInFormat, session.Username); return(FtpResponsesAsync.LoggedIn); }
/// <summary> /// Enumerates list of items in the directory and sends them to the client over the data channel. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <returns>A <see cref="Task"/> that represents an asynchronous operation.</returns> protected override async Task HandleDataCommand(string arguments, FtpSessionState session, CancellationToken cancellation) { IEnumerable <FileSystemItem> items = null; if (String.IsNullOrEmpty(arguments) || ListAll.Equals(arguments, StringComparison.OrdinalIgnoreCase)) { items = await session.FileSystem.ListItems(session.CurrentDirectory, cancellation); } else { var itemPath = session.CurrentDirectory.Clone(); if (itemPath.Navigate(arguments)) { var item = await session.FileSystem.GetItem(itemPath, cancellation); if (item != null) { items = new[] { item }; } } } if (items == null) { await session.ControlChannel.Send(FtpResponses.FileUnavailable, cancellation); return; } await WriteToDataChannel(session, WriteFileList, items, cancellation); }
/// <summary> /// Enumerates list of item names in the directory and sends them to the client over the data channel. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <returns>A <see cref="Task"/> that represents an asynchronous operation.</returns> protected override async Task HandleDataCommand(string arguments, FtpSessionState session, CancellationToken cancellation) { var requestedPath = session.CurrentDirectory; if (!String.IsNullOrEmpty(arguments)) { requestedPath = requestedPath.Clone(); if (!requestedPath.Navigate(arguments)) { await session.ControlChannel.Send(FtpResponses.FileUnavailable, cancellation); return; } } var items = await session.FileSystem.ListItems(requestedPath, cancellation); if (items == null) { await session.ControlChannel.Send(FtpResponses.FileUnavailable, cancellation); return; } await WriteToDataChannel(session, WriteFileList, items, cancellation); }
/// <summary> /// Renames the file in the file system. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override async Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { var sourcePath = session.RenameSource; session.RenameSource = null; if (String.IsNullOrEmpty(arguments)) { return(FtpResponses.ParameterSyntaxError); } var targetPath = session.CurrentDirectory.Clone(); if (targetPath.Navigate(arguments)) { if (await session.FileSystem.IsFileExist(sourcePath, cancellation) && await session.FileSystem.RenameFile(sourcePath, targetPath, cancellation)) { session.Logger.WriteInfo(TraceResources.RenamedFileFormat, sourcePath, targetPath); return(FtpResponses.FileActionOk); } if (await session.FileSystem.IsDirectoryExist(sourcePath, cancellation) && await session.FileSystem.RenameDirectory(sourcePath, targetPath, cancellation)) { session.Logger.WriteInfo(TraceResources.RenamedDirectoryFormat, sourcePath, targetPath); return(FtpResponses.FileActionOk); } } return(FtpResponses.FileUnavailable); }
/// <summary> /// Obtains the size of the file in the file system. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override async Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { if (String.IsNullOrEmpty(arguments)) { return(FtpResponses.ParameterSyntaxError); } if (session.TransferType != FileTransferType.Image) { // Only return size in Image file transfer mode return(FtpResponses.FileUnavailable); } var itemPath = session.CurrentDirectory.Clone(); FileSystemItem item = null; if (itemPath.Navigate(arguments)) { item = await session.FileSystem.GetItem(itemPath, cancellation); } if (item == null || item.IsDirectory) { return(FtpResponses.FileUnavailable); } return(FtpResponses.FileStatus(item.Size.ToString(CultureInfo.InvariantCulture))); }
/// <summary> /// Obtains the restart offset from the FTP session context and transfers the file. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>A <see cref="Task"/> that represents an asynchronous operation.</returns> protected sealed override Task HandleDataCommand(string arguments, FtpSessionState session, CancellationToken cancellation) { var restartOffset = session.TransferRestartOffset; session.TransferRestartOffset = 0; return(HandleFileTransferCommand(arguments, session, restartOffset, cancellation)); }
/// <summary> /// Verifies that provided user has access to the server. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { if (!knownUsers.Any(u => u.Name == arguments)) { return(FtpResponsesAsync.InvalidUsername); } session.Username = arguments; return(FtpResponsesAsync.UserOk); }
/// <summary> /// Verifies user authentication status and performs all necessary actions for the command. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>A <see cref="Task"/> that represents an asynchronous operation.</returns> public override async Task Handle(IArrayBufferView arguments, FtpSessionState session, CancellationToken cancellation) { if (session.FileSystem == null) { await session.ControlChannel.Send(FtpResponses.NotLoggedIn, cancellation); return; } await base.Handle(arguments, session, cancellation); }
/// <summary> /// Closes the active data channel. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { try { session.DataChannel.Dispose(); } catch { } session.DataChannel = null; return(FtpResponsesAsync.TransferComplete); }
/// <summary> /// Changes the current path to the parent directory. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <returns>FTP server response to send to the client.</returns> protected override async Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { var newPath = session.CurrentDirectory.Clone(); if (newPath.NavigateUp() && await session.FileSystem.IsDirectoryExist(newPath, cancellation)) { session.CurrentDirectory = newPath; return(FtpResponses.FileActionOk); } return(FtpResponses.FileUnavailable); }
/// <summary> /// Sends the requested file to the client over the data channel. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="restartOffset">File transfer restart offset</param> /// <param name="cancellation">Cancellation token</param> /// <returns>A <see cref="Task"/> that represents an asynchronous operation.</returns> protected override async Task HandleFileTransferCommand( string arguments, FtpSessionState session, long restartOffset, CancellationToken cancellation) { if (String.IsNullOrEmpty(arguments)) { await session.ControlChannel.Send(FtpResponses.ParameterSyntaxError, cancellation); return; } var filePath = session.CurrentDirectory.Clone(); if (filePath.Navigate(arguments)) { using (var fileStream = await session.FileSystem.ReadFile(filePath, cancellation)) { if (fileStream == null) { await session.ControlChannel.Send(FtpResponses.FileUnavailable, cancellation); return; } if (restartOffset > 0) { try { await fileStream.SetOffset(restartOffset); } catch { // If offset is out of range await session.ControlChannel.Send(FtpResponses.FileUnavailable, cancellation); return; } } if (await WriteToDataChannel(session, SendFile, fileStream, cancellation) == FtpResponses.TransferComplete) { session.Logger.WriteInfo(TraceResources.RetrievedFileFormat, filePath); } } } else { await session.ControlChannel.Send(FtpResponses.FileUnavailable, cancellation); } }
/// <summary> /// Sends the file contents to the client over the data channel. /// </summary> /// <param name="dataStream">Data channel stream</param> /// <param name="session">FTP session context</param> /// <param name="fileStream">Source file stream</param> /// <param name="cancellation">Cancellation token</param> /// <returns>Always returns <see cref="FtpResponses.TransferComplete"/>.</returns> private async Task <IResponse> SendFile( Stream dataStream, FtpSessionState session, Stream fileStream, CancellationToken cancellation) { await fileStream.CopyToAsync(dataStream); await dataStream.FlushAsync(cancellation); return(FtpResponses.TransferComplete); }
/// <summary> /// Stores the offset to restart the next file transfer from in the FTP session context. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { long offset; if (!long.TryParse(arguments, out offset)) { return(FtpResponsesAsync.ParameterSyntaxError); } session.TransferRestartOffset = offset; return(FtpResponsesAsync.FileMoreInfoRequired); }
/// <summary> /// Verifies if requested file structure is supported by the server. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { if (String.IsNullOrEmpty(arguments)) { return(FtpResponsesAsync.ParameterSyntaxError); } if (FileStructure.Equals(arguments, StringComparison.OrdinalIgnoreCase)) { return(FtpResponsesAsync.Success); } return(FtpResponsesAsync.NotImplementedForParameter); }
/// <summary> /// Creates a new passive mode data channel for the FTP session. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { var channel = new PassiveDataChannel( new IPEndPoint(session.ServerAddress, 0), new LoggerScope(TraceResources.PassiveModeLoggerScope, session.Logger)); session.DataChannel = channel; return (FtpResponsesAsync.PassiveMode( session.PublicServerAddress.GetAddressBytes(), channel.EndPoint.Port)); }
/// <summary> /// Changes the current path to the new one. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <returns>FTP server response to send to the client.</returns> protected override async Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { if (String.IsNullOrEmpty(arguments)) { return(FtpResponses.ParameterSyntaxError); } var newPath = session.CurrentDirectory.Clone(); if (newPath.Navigate(arguments) && await session.FileSystem.IsDirectoryExist(newPath, cancellation)) { session.CurrentDirectory = newPath; return(FtpResponses.FileActionOk); } return(FtpResponses.FileUnavailable); }
/// <summary> /// Deletes a file from the file system. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override async Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { if (String.IsNullOrEmpty(arguments)) { return(FtpResponses.ParameterSyntaxError); } var itemPath = session.CurrentDirectory.Clone(); if (itemPath.Navigate(arguments) && await session.FileSystem.RemoveFile(itemPath, cancellation)) { session.Logger.WriteInfo(TraceResources.DeletedFileFormat, itemPath); return(FtpResponses.FileActionOk); } return(FtpResponses.FileUnavailable); }
/// <summary> /// Writes data to the currently active data channel. /// </summary> /// <typeparam name="TState">Type of the state object to pass to writer delegate.</typeparam> /// <param name="session">FTP session state</param> /// <param name="writer">Delegate that writes the actual data to the data channel</param> /// <param name="state">State object to pass to writer delegate</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response.</returns> protected async Task <IResponse> WriteToDataChannel <TState>( FtpSessionState session, Func <Stream, FtpSessionState, TState, CancellationToken, Task <IResponse> > writer, TState state, CancellationToken cancellation) { var dataChannel = session.DataChannel; if (dataChannel == null) { await session.ControlChannel.Send(FtpResponses.CanNotOpenDataChannel, cancellation); return(FtpResponses.CanNotOpenDataChannel); } IResponse transferResult; try { using (var dataStream = await dataChannel.GetDataStream()) { await session.ControlChannel.Send(FtpResponses.OpenningDataChannel, cancellation); transferResult = await writer(dataStream, session, state, cancellation); } } catch (ObjectDisposedException) { await session.ControlChannel.Send(FtpResponses.CanNotOpenDataChannel, cancellation); return(FtpResponses.CanNotOpenDataChannel); } try { session.DataChannel.Dispose(); } catch { } session.DataChannel = null; await session.ControlChannel.Send(transferResult, cancellation); return(transferResult); }
/// <summary> /// Logs out current user without disconnecting the client socket. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { try { session.DataChannel.Dispose(); } catch { } session.DataChannel = null; session.Username = null; session.FileSystem = null; session.CurrentDirectory = null; session.PathEncoding = Encoding.ASCII; session.TransferType = FileTransferType.ASCII; return(FtpResponsesAsync.ServiceReady); }
/// <summary> /// Creates a new directory in the file system. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override async Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { if (String.IsNullOrEmpty(arguments)) { return(FtpResponses.ParameterSyntaxError); } var itemPath = session.CurrentDirectory.Clone(); if (itemPath.Navigate(arguments) && await session.FileSystem.CreateDirectory(itemPath, cancellation)) { var itemPathString = itemPath.ToString(); session.Logger.WriteInfo(TraceResources.CreatedDirectoryFormat, itemPathString); return(FtpResponses.Path(itemPathString, session.PathEncoding)); } return(FtpResponses.FileUnavailable); }
/// <summary> /// Receives the file from the client over the data channel. /// </summary> /// <param name="dataStream">Data channel stream</param> /// <param name="session">FTP session context</param> /// <param name="fileStream">Target file stream</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP response that should be sent to the client.</returns> private async Task <IResponse> ReceiveFile( Stream dataStream, FtpSessionState session, Stream fileStream, CancellationToken cancellation) { try { await dataStream.CopyToAsync(fileStream); await fileStream.FlushAsync(cancellation); return(FtpResponses.TransferComplete); } catch (NotEnoughSpaceException) { return(FtpResponses.NotEnoughSpace); } }
/// <summary> /// Stores the original file name in the FTP session context. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override async Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { if (String.IsNullOrEmpty(arguments)) { return(FtpResponses.ParameterSyntaxError); } var sourcePath = session.CurrentDirectory.Clone(); if (sourcePath.Navigate(arguments) && (await session.FileSystem.IsFileExist(sourcePath, cancellation) || await session.FileSystem.IsDirectoryExist(sourcePath, cancellation))) { session.RenameSource = sourcePath; return(FtpResponses.FileMoreInfoRequired); } return(FtpResponses.FileUnavailable); }
/// <summary> /// Updates the current FTP session context with the requested options. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { var optionArguments = arguments.Split(' '); if (optionArguments.Length == 0) { return(FtpResponsesAsync.ParameterSyntaxError); } if (FtpOptions.UTF8.Equals(optionArguments[0], StringComparison.OrdinalIgnoreCase)) { if (optionArguments.Length == 1) { return(FtpResponsesAsync.ParameterSyntaxError); } if (FtpOptions.SetOn.Equals(optionArguments[1], StringComparison.OrdinalIgnoreCase)) { session.PathEncoding = Encoding.UTF8; } else if (FtpOptions.SetOff.Equals(optionArguments[1], StringComparison.OrdinalIgnoreCase)) { session.PathEncoding = Encoding.ASCII; } else { return(FtpResponsesAsync.ParameterSyntaxError); } return(FtpResponsesAsync.Success); } else if (FtpOptions.UTF_8.Equals(optionArguments[0], StringComparison.OrdinalIgnoreCase)) { session.PathEncoding = Encoding.UTF8; // TODO: Support NLST argument return(FtpResponsesAsync.Success); } else { return(FtpResponsesAsync.ParameterSyntaxError); } }
/// <summary> /// Sends the list of file system item names to the client over the data channel. /// </summary> /// <param name="dataStream">Data channel stream</param> /// <param name="session">FTP session context</param> /// <param name="items">File system items</param> /// <param name="cancellation">Cancellation token</param> /// <returns>Always returns <see cref="FtpResponses.TransferComplete"/>.</returns> private async Task <IResponse> WriteFileList( Stream dataStream, FtpSessionState session, IEnumerable <FileSystemItem> items, CancellationToken cancellation) { byte[] recordBuffer; foreach (var item in items) { recordBuffer = session.PathEncoding.GetBytes(item.Name); await dataStream.WriteAsync(recordBuffer, 0, recordBuffer.Length, cancellation); await dataStream.WriteAsync(session.LineFeed, 0, session.LineFeed.Length, cancellation); } await dataStream.WriteAsync(session.LineFeed, 0, session.LineFeed.Length, cancellation); await dataStream.FlushAsync(cancellation); return(FtpResponses.TransferComplete); }
/// <summary> /// Creates a new active mode data channel for the FTP session. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { if (String.IsNullOrEmpty(arguments)) { return(FtpResponsesAsync.ParameterSyntaxError); } var segments = arguments.Split(','); EndPoint endPoint; if (!TryGetEndPoint(segments, out endPoint)) { return(FtpResponsesAsync.ParameterSyntaxError); } session.DataChannel = new ActiveDataChannel( endPoint, new LoggerScope(TraceResources.ActiveModeLoggerScope, session.Logger)); return(FtpResponsesAsync.Success); }
/// <summary> /// Updates the transfer type in the FTP session context. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { var typeArguments = arguments.Split(' '); if (typeArguments.Length == 0) { return(FtpResponsesAsync.ParameterSyntaxError); } switch (typeArguments[0]) { case AsciiTransferType: session.TransferType = FileTransferType.ASCII; if (typeArguments.Length > 1) { switch (typeArguments[1]) { case NonPrintFormat: return(FtpResponsesAsync.Success); default: return(FtpResponsesAsync.NotImplementedForParameter); } } return(FtpResponsesAsync.Success); case ImageTransferType: session.TransferType = FileTransferType.Image; return(FtpResponsesAsync.Success); default: return(FtpResponsesAsync.NotImplementedForParameter); } }
/// <summary> /// Obtains the last modification time of the file system item. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response to send to the client.</returns> protected override async Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { if (String.IsNullOrEmpty(arguments)) { return(FtpResponses.ParameterSyntaxError); } var itemPath = session.CurrentDirectory.Clone(); FileSystemItem item = null; if (itemPath.Navigate(arguments)) { item = await session.FileSystem.GetItem(itemPath, cancellation); } if (item == null) { return(FtpResponses.FileUnavailable); } return(FtpResponses.FileStatus( item.LastModifiedTime.ToString(TimestampFormat, CultureInfo.InvariantCulture))); }
/// <summary> /// Converts command arguments from binary form to string using current paths encoding. /// </summary> /// <param name="arguments">Arguments in binary form</param> /// <param name="session">FTP session context</param> /// <returns>Command arguments as string.</returns> protected override string ReadArguments(IArrayBufferView arguments, FtpSessionState session) { return(arguments.ToString(session.PathEncoding)); }
/// <summary> /// Performs all necessary actions for the data command. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session context</param> /// <param name="cancellation">Cancellation token</param> /// <returns>A <see cref="Task"/> that represents an asynchronous operation.</returns> protected abstract Task HandleDataCommand(string arguments, FtpSessionState session, CancellationToken cancellation);
/// <summary> /// This method is not used by data commands, as data commands return multiple responses during the data transfer. /// </summary> /// <param name="arguments">Command arguments</param> /// <param name="session">FTP session state</param> /// <param name="cancellation">Cancellation token</param> /// <returns>FTP server response.</returns> protected sealed override Task <IResponse> Handle(string arguments, FtpSessionState session, CancellationToken cancellation) { throw new NotImplementedException(); }