/// <summary> /// Closes the write stream and associated socket (if open), /// </summary> /// <param name="ctsToken"></param> /// <returns></returns> public async Task CloseFileDataStreamAsync(CancellationToken ctsToken = default(CancellationToken)) { Logger?.LogDebug("[FtpClient] Closing write file stream"); dataStream.Dispose(); await ControlStream.GetResponseAsync(ctsToken); }
/// <summary> /// Renames a file on the FTP server /// </summary> /// <param name="from"></param> /// <param name="to"></param> /// <returns></returns> public async Task RenameAsync(string from, string to) { EnsureLoggedIn(); Logger?.LogDebug($"[FtpClient] Renaming from {from}, to {to}"); var renameFromResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = FtpCommand.RNFR, Data = from }); if (renameFromResponse.FtpStatusCode != FtpStatusCode.FileCommandPending) { throw new FtpException(renameFromResponse.ResponseMessage); } var renameToResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = FtpCommand.RNTO, Data = to }); if (renameToResponse.FtpStatusCode != FtpStatusCode.FileActionOK && renameToResponse.FtpStatusCode != FtpStatusCode.ClosingData) { throw new FtpException(renameFromResponse.ResponseMessage); } }
/// <summary> /// Changes the working directory to the given value for the current session /// </summary> /// <param name="directory"></param> /// <returns></returns> public async Task ChangeWorkingDirectoryAsync(string directory) { Logger?.LogTrace($"[FtpClient] changing directory to {directory}"); if (directory.IsNullOrWhiteSpace() || directory.Equals(".")) { throw new ArgumentOutOfRangeException(nameof(directory), "Directory supplied was incorrect"); } EnsureLoggedIn(); var response = await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = FtpCommand.CWD, Data = directory }); if (!response.IsSuccess) { throw new FtpException(response.ResponseMessage); } var pwdResponse = await ControlStream.SendCommandAsync(FtpCommand.PWD); if (!response.IsSuccess) { throw new FtpException(response.ResponseMessage); } WorkingDirectory = pwdResponse.ResponseMessage.Split('"')[1]; }
/// <summary> /// Produces a data socket using Passive (PASV) or Extended Passive (EPSV) mode /// </summary> /// <returns></returns> internal async Task <Stream> ConnectDataStreamAsync() { Logger?.LogTrace("[FtpClient] Connecting to a data socket"); var epsvResult = await ControlStream.SendCommandAsync(FtpCommand.EPSV); int?passivePortNumber; if (epsvResult.FtpStatusCode == FtpStatusCode.EnteringExtendedPassive) { passivePortNumber = epsvResult.ResponseMessage.ExtractEpsvPortNumber(); } else { // EPSV failed - try regular PASV var pasvResult = await ControlStream.SendCommandAsync(FtpCommand.PASV); if (pasvResult.FtpStatusCode != FtpStatusCode.EnteringPassive) { throw new FtpException(pasvResult.ResponseMessage); } passivePortNumber = pasvResult.ResponseMessage.ExtractPasvPortNumber(); } if (!passivePortNumber.HasValue) { throw new FtpException("Could not determine EPSV/PASV data port"); } return(await ControlStream.OpenDataStreamAsync(Configuration.Host, passivePortNumber.Value, CancellationToken.None)); }
public void Dispose() { Logger?.LogDebug("Disposing of FtpClient"); Task.WaitAny(LogOutAsync()); ControlStream?.Dispose(); dataSocketSemaphore?.Dispose(); }
/// <summary> /// Attempts to log the user in to the FTP Server /// </summary> /// <returns></returns> public async Task LoginAsync() { if (IsConnected) { await LogOutAsync(); } string username = Configuration.Username.IsNullOrWhiteSpace() ? Constants.ANONYMOUS_USER : Configuration.Username; await ControlStream.ConnectAsync(); var usrResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = FtpCommand.USER, Data = username }); await BailIfResponseNotAsync(usrResponse, FtpStatusCode.SendUserCommand, FtpStatusCode.SendPasswordCommand, FtpStatusCode.LoggedInProceed); var passResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = FtpCommand.PASS, Data = username != Constants.ANONYMOUS_USER ? Configuration.Password : string.Empty }); await BailIfResponseNotAsync(passResponse, FtpStatusCode.LoggedInProceed); IsAuthenticated = true; if (ControlStream.IsEncrypted) { await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = FtpCommand.PBSZ, Data = "0" }); await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = FtpCommand.PROT, Data = "P" }); } Features = await DetermineFeaturesAsync(); directoryProvider = DetermineDirectoryProvider(); await EnableUTF8IfPossible(); await SetTransferMode(Configuration.Mode, Configuration.ModeSecondType); if (Configuration.BaseDirectory != "/") { await CreateDirectoryAsync(Configuration.BaseDirectory); } await ChangeWorkingDirectoryAsync(Configuration.BaseDirectory); }
public void Dispose() { DataStream?.Close(); DataStream?.Dispose(); ControlStream?.Close(); ControlStream?.Dispose(); IsAlive = false; }
private async Task <int> executeCommandAsync(string command, string data = null, bool getResponse = true, bool force = false) { ConnectTicker = 0; if (IsCanceled && !force) { commandIn = false; return(0); } int response = 0; string fullCommand; if (data.NullEmpty()) { fullCommand = command; } else { fullCommand = command + ' ' + data; } if (commandIn) { while (commandIn && !IsCanceled) { await Task.Delay(200); } } commandIn = true; byte[] buffer = _encoding.GetBytes(fullCommand + "\r\n"); try { await ControlStream.WriteAsync(buffer, 0, buffer.Length); } catch { commandIn = false; return(0); } if (getResponse) { bool notify = true; if ("PASS FEAT CLNT SYST".Contains(command)) { if (command == "PASS") { InfoMessage("PASS *** Hidden ***", MessageType.Sent); } else { notify = false; } } else { InfoMessage(fullCommand, MessageType.Sent); } response = await getResponseAsync(notify, force); } commandIn = false; return(response); }
/// <summary> /// Ignore any stale data we may have waiting on the stream /// </summary> /// <returns></returns> private async Task IgnoreStaleData() { if (IsConnected && ControlStream.SocketDataAvailable()) { var staleData = await ControlStream.GetResponseAsync(); Logger?.LogWarning($"Stale data detected: {staleData.ResponseMessage}"); } }
/// <summary> /// Closes the write stream and associated socket (if open), /// </summary> /// <param name="ctsToken"></param> /// <returns></returns> public async Task CloseFileDataStreamAsync(CancellationToken ctsToken = default(CancellationToken)) { Logger?.LogTrace("[FTPClient] Closing write file stream"); dataStream.Dispose(); if (ControlStream != null) { await ControlStream.GetResponseAsync(ctsToken); } }
/// <summary> /// Informs the FTP server of the client being used /// </summary> /// <param name="clientName"></param> /// <returns></returns> public async Task <FtpResponse> SetClientName(string clientName) { EnsureLoggedIn(); Logger?.LogDebug($"[FtpClient] Setting client name to {clientName}"); return(await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = FtpCommand.CLNT, Data = clientName })); }
/// <summary> /// Lists all directories in the current working directory /// </summary> /// <returns></returns> public async Task <ReadOnlyCollection <FtpNodeInformation> > ListDirectoriesAsync() { try { EnsureLoggedIn(); Logger?.LogDebug($"[FtpClient] Listing directories in {WorkingDirectory}"); return(await directoryProvider.ListDirectoriesAsync()); } finally { await ControlStream.GetResponseAsync(); } }
/// <summary> /// Lists all files in the current working directory /// </summary> /// <returns></returns> public async Task <IEnumerable <FtpNodeInformation> > ListFilesAsync() { try { EnsureLoggedIn(); LoggerHelper.Debug($"[FtpClient] Listing files in {WorkingDirectory}"); return(await directoryProvider.ListFilesAsync()); } finally { await ControlStream.GetResponseAsync(); } }
public void AddSiteStreamUser_Returns_ControlStream() { const ulong UserID = 1; const string StreamID = "1_1_54e345d655ee3e8df359ac033648530bfbe26c5f"; var ctx = InitializeTwitterContext(); ControlStream cs = ctx.AddSiteStreamUser(new List <ulong> { UserID }, StreamID); Assert.NotNull(cs); Assert.Equal(CommandResponse, cs.CommandResponse); }
/// <summary> /// Lists all directories in the current working directory /// </summary> /// <param name="fileName"></param> /// <returns></returns> public async Task DeleteFileAsync(string fileName) { EnsureLoggedIn(); Logger?.LogDebug($"[FtpClient] Deleting file {fileName}"); var response = await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = FtpCommand.DELE, Data = fileName }); if (!response.IsSuccess) { throw new FtpException(response.ResponseMessage); } }
/// <summary> /// Determine if the FTP server supports UTF8 encoding, and set it to the default if possible /// </summary> /// <returns></returns> private async Task EnableUTF8IfPossible() { if (Equals(ControlStream.Encoding, Encoding.ASCII) && Features.Any(x => x == Constants.UTF8)) { ControlStream.Encoding = Encoding.UTF8; } if (Equals(ControlStream.Encoding, Encoding.UTF8)) { // If the server supports UTF8 it should already be enabled and this // command should not matter however there are conflicting drafts // about this so we'll just execute it to be safe. await ControlStream.SendCommandAsync("OPTS UTF8 ON"); } }
/// <summary> /// Attemps to log the user out asynchronously, sends the QUIT command and terminates the command socket. /// </summary> public async Task LogOutAsync() { await IgnoreStaleData(); if (!IsConnected) { return; } Logger?.LogTrace("[FtpClient] Logging out"); await ControlStream.SendCommandAsync(FtpCommand.QUIT); ControlStream.Disconnect(); IsAuthenticated = false; }
public async Task WriteElevationRequest(ElevationRequest elevationRequest) { // Using Binary instead of Newtonsoft.JSON to reduce load times. var ms = new System.IO.MemoryStream(); new BinaryFormatter() { TypeFormat = System.Runtime.Serialization.Formatters.FormatterTypeStyle.TypesAlways, Binder = new MySerializationBinder() } .Serialize(ms, elevationRequest); ms.Seek(0, System.IO.SeekOrigin.Begin); byte[] lengthArray = BitConverter.GetBytes(ms.Length); Logger.Instance.Log($"ElevationRequest length {ms.Length}", LogLevel.Debug); await ControlStream.WriteAsync(lengthArray, 0, sizeof(int)).ConfigureAwait(false); await ControlStream.WriteAsync(ms.ToArray(), 0, (int)ms.Length).ConfigureAwait(false); await ControlStream.FlushAsync().ConfigureAwait(false); }
/// <summary> /// Determines the file size of the given file /// </summary> /// <param name="transferMode"></param> /// <param name="secondType"></param> /// <returns></returns> public async Task SetTransferMode(FtpTransferMode transferMode, char secondType = '\0') { EnsureLoggedIn(); Logger?.LogTrace($"[FtpClient] Setting transfer mode {transferMode}, {secondType}"); var response = await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = FtpCommand.TYPE, Data = secondType != '\0' ? $"{(char) transferMode} {secondType}" : $"{(char) transferMode}" }); if (!response.IsSuccess) { throw new FtpException(response.ResponseMessage); } }
private async Task <IEnumerable <string> > DetermineFeaturesAsync() { EnsureLoggedIn(); Logger?.LogTrace("[FTPClient] Determining features"); var response = await ControlStream.SendCommandAsync(FTPCommand.FEAT); if (response.FTPStatusCode == FTPStatusCode.COMMANDSYNTAXERROR || response.FTPStatusCode == FTPStatusCode.COMMANDNOTIMPLEMENTED) { return(Enumerable.Empty <string>()); } var features = response.Data.Where(x => !x.StartsWith(((int)FTPStatusCode.SYSTEMHELPREPLY).ToString()) && !x.IsNullOrWhiteSpace()) .Select(x => x.Replace(Constants.CARRIAGE_RETURN, string.Empty).Trim()) .ToList(); return(features); }
private async Task <IEnumerable <string> > DetermineFeaturesAsync() { EnsureLoggedIn(); Logger?.LogTrace("[FtpClient] Determining features"); var response = await ControlStream.SendCommandAsync(FtpCommand.FEAT); if (response.FtpStatusCode == FtpStatusCode.CommandSyntaxError || response.FtpStatusCode == FtpStatusCode.CommandNotImplemented) { return(Enumerable.Empty <string>()); } var features = response.Data.Where(x => !x.StartsWith(((int)FtpStatusCode.SystemHelpReply).ToString()) && !x.IsNullOrWhiteSpace()) .Select(x => x.Replace(Constants.CARRIAGE_RETURN, string.Empty).Trim()) .ToList(); return(features); }
/// <summary> /// 暂不支持EPRT /// 1:ipV4;2 ipV6 /// EPRT |1|132.235.1.2|6275| /// EPRT |2|1080::8:800:200C:417A|5282| /// /// PORT 192,168,191,11,206,97 /// </summary> internal async Task <Stream> PortDataStreamAsync() { // 主动模式时,客户端必须告知服务器接收数据的端口号,PORT 命令格式为:PORT address //PORT 192,168,191,11,206,97 // address参数的格式为i1、i2、i3、i4、p1、p2,其中i1、i2、i3、i4表示IP地址 // 下面通过.字符串来组合这四个参数得到IP地址 System.Net.Sockets.TcpListener dataListener; string portListener; string sendString = string.Empty; var localip = (System.Net.IPAddress)LocalEndPoint; Random random = new Random(); int random1, random2; int port; string ip = string.Empty; while (true) { // 随机生成一个端口进行数据传输 random1 = random.Next(5, 200); random2 = random.Next(0, 200); // 生成的端口号控制>1024的随机端口 // 下面这个运算算法只是为了得到一个大于1024的端口值 port = random1 << 8 | random2; try { dataListener = new System.Net.Sockets.TcpListener(localip, port); dataListener.Start(); } catch { continue; } ip = localip.ToString().Replace('.', ','); portListener = string.Format("{0},{1},{2}", ip, random1, random2); // 必须把端口号IP地址告诉客户端,客户端接收到响应命令后, // 再通过新的端口连接服务器的端口P,然后进行文件数据传输 break; } var pasvResult = await ControlStream.SendCommandAsync(new FtpCommandEnvelope() { FtpCommand = FtpCommand.PORT, Data = portListener }); return(await ControlStream.AcceptDataStreamAsync(dataListener)); }
/// <summary> /// Attemps to log the user out asynchronously, sends the QUIT command and terminates the command socket. /// </summary> public async Task LogOutAsync() { await IgnoreStaleData(); if (!IsConnected) { return; } LoggerHelper.Trace("[FtpClient] Logging out"); await ControlStream.SendCommandAsync(FtpCommand.QUIT); ControlStream.Disconnect(); if (LocalEndPoint != null) { LocalEndPoint = null; } IsAuthenticated = false; }
/// <summary> /// Determines the file size of the given file /// </summary> /// <param name="fileName"></param> /// <returns></returns> public async Task <long> GetFileSizeAsync(string fileName) { EnsureLoggedIn(); Logger?.LogDebug($"[FtpClient] Getting file size for {fileName}"); var sizeResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = FtpCommand.SIZE, Data = fileName }); if (sizeResponse.FtpStatusCode != FtpStatusCode.FileStatus) { throw new FtpException(sizeResponse.ResponseMessage); } long fileSize = long.Parse(sizeResponse.ResponseMessage); return(fileSize); }
public void Abort(ConnectionAbortedException ex) { lock (_sync) { if (ControlStream != null) { // TODO need to await this somewhere or allow this to be called elsewhere? ControlStream.SendGoAway(_highestOpenedStreamId).GetAwaiter().GetResult(); } } _haveSentGoAway = true; // Abort currently active streams foreach (var stream in _streams.Values) { stream.Abort(new ConnectionAbortedException("The Http3Connection has been aborted"), Http3ErrorCode.UnexpectedFrame); } // TODO need to figure out if there is server initiated connection close rather than stream close? }
protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { if (ControlClient != null) { ControlClient.Close(); } if (ControlStream != null) { ControlStream.Close(); } } } _disposed = true; OnDisposed(); }
/// <summary> /// Opens a filestream to the given filename /// </summary> /// <param name="fileName"></param> /// <param name="command"></param> /// <returns></returns> private async Task <Stream> OpenFileStreamAsync(string fileName, FTPCommand command) { EnsureLoggedIn(); Logger?.LogDebug($"[FTPClient] Opening filestream for {fileName}, {command}"); dataStream = await ConnectDataStreamAsync(); var retrResponse = await ControlStream.SendCommandAsync(new FTPCommandEnvelope { FTPCommand = command, Data = fileName }); if ((retrResponse.FTPStatusCode != FTPStatusCode.DATAALREADYOPEN) && (retrResponse.FTPStatusCode != FTPStatusCode.OPENINGDATA) && (retrResponse.FTPStatusCode != FTPStatusCode.CLOSINGDATA)) { throw new FTPException(retrResponse.ResponseMessage); } return(dataStream); }
/// <summary> /// Opens a filestream to the given filename /// </summary> /// <param name="fileName"></param> /// <param name="command"></param> /// <returns></returns> private async Task <Stream> OpenFileStreamAsync(string fileName, FtpCommand command) { EnsureLoggedIn(); Logger?.LogDebug($"[FtpClient] Opening filestream for {fileName}, {command}"); dataStream = await ConnectDataStreamAsync(); var retrResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = command, Data = fileName }); if ((retrResponse.FtpStatusCode != FtpStatusCode.DataAlreadyOpen) && (retrResponse.FtpStatusCode != FtpStatusCode.OpeningData) && (retrResponse.FtpStatusCode != FtpStatusCode.ClosingData)) { throw new FtpException(retrResponse.ResponseMessage); } return(dataStream); }
/// <summary> /// Deletes the given directory from the FTP server /// </summary> /// <param name="directory"></param> /// <returns></returns> public async Task DeleteDirectoryAsync(string directory) { if (directory.IsNullOrWhiteSpace() || directory.Equals(".")) { throw new ArgumentOutOfRangeException(nameof(directory), "Directory supplied was not valid"); } if (directory == "/") { return; } Logger?.LogDebug($"[FtpClient] Deleting directory {directory}"); EnsureLoggedIn(); var rmdResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope { FtpCommand = FtpCommand.RMD, Data = directory }); switch (rmdResponse.FtpStatusCode) { case FtpStatusCode.CommandOK: case FtpStatusCode.FileActionOK: return; case FtpStatusCode.ActionNotTakenFileUnavailable: await DeleteNonEmptyDirectory(directory); return; default: throw new FtpException(rmdResponse.ResponseMessage); } }
internal async Task InnerProcessRequestsAsync <TContext>(IHttpApplication <TContext> application) { // Start other three unidirectional streams here. var controlTask = CreateControlStream(application); var encoderTask = CreateEncoderStream(application); var decoderTask = CreateDecoderStream(application); try { while (true) { var streamContext = await _multiplexedContext.AcceptAsync(); if (streamContext == null || _haveSentGoAway) { break; } var quicStreamFeature = streamContext.Features.Get <IStreamDirectionFeature>(); var streamIdFeature = streamContext.Features.Get <IStreamIdFeature>(); Debug.Assert(quicStreamFeature != null); var httpConnectionContext = new Http3StreamContext { ConnectionId = streamContext.ConnectionId, StreamContext = streamContext, // TODO connection context is null here. Should we set it to anything? ServiceContext = _context.ServiceContext, ConnectionFeatures = streamContext.Features, MemoryPool = _context.MemoryPool, Transport = streamContext.Transport, TimeoutControl = _context.TimeoutControl, LocalEndPoint = streamContext.LocalEndPoint as IPEndPoint, RemoteEndPoint = streamContext.RemoteEndPoint as IPEndPoint }; if (!quicStreamFeature.CanWrite) { // Unidirectional stream var stream = new Http3ControlStream <TContext>(application, this, httpConnectionContext); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } else { // Keep track of highest stream id seen for GOAWAY var streamId = streamIdFeature.StreamId; HighestStreamId = streamId; var http3Stream = new Http3Stream <TContext>(application, this, httpConnectionContext); var stream = http3Stream; lock (_streams) { _streams[streamId] = http3Stream; } KestrelEventSource.Log.RequestQueuedStart(stream, AspNetCore.Http.HttpProtocol.Http3); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } } } finally { // Abort all streams as connection has shutdown. lock (_streams) { foreach (var stream in _streams.Values) { stream.Abort(new ConnectionAbortedException("Connection is shutting down.")); } } ControlStream?.Abort(new ConnectionAbortedException("Connection is shutting down.")); EncoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down.")); DecoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down.")); await controlTask; await encoderTask; await decoderTask; } }