internal ReceiveState(CommandStream connection) { this.Connection = connection; this.Resp = new ResponseDescription(); this.Buffer = new byte[0x400]; this.ValidThrough = 0; }
protected void InitCommandPipeline(WebRequest request, PipelineEntry[] commands, bool isAsync) { _commands = commands; _index = 0; _request = request; _aborted = false; _doRead = true; _doSend = true; _currentResponseDescription = null; _isAsync = isAsync; _recoverableFailure = false; _abortReason = string.Empty; }
protected void InitCommandPipeline(WebRequest request, PipelineEntry [] commands, bool async) { m_Commands = commands; m_Index = 0; m_Request = request; m_Aborted = false; m_DoRead = true; m_DoSend = true; m_CurrentResponseDescription = null; m_Async = async; m_RecoverableFailure = false; m_AbortReason = string.Empty; }
// private bool PostSendCommandProcessing(ref Stream stream) { /* ** I don;t see how this code can be still relevant, remove it of no problems observed ** ** ** // ** // This is a general race condition in [....] mode, if the server returns an error ** // after we open the data connection, we will be off reading the data connection, ** // and not the control connection. The best we can do is try to poll, and in the ** // the worst case, we will timeout on establishing the data connection. ** // ** if (!m_DoRead && !m_Async) { ** m_DoRead = Poll(100 * 1000, SelectMode.SelectRead); // Poll is in Microseconds. ** } */ if (m_DoRead) { // In async case, The next call can actually result in a // series of synchronous completions that eventually close // the connection. So we need to save the members that // we need to access, since they may not be valid after the // next call returns bool async = m_Async; int index = m_Index; PipelineEntry[] commands = m_Commands; try { ResponseDescription response = ReceiveCommandResponse(); if (async) { return(true); } m_CurrentResponseDescription = response; } catch { // If we get an exception on the QUIT command (which is // always the last command), ignore the final exception // and continue with the pipeline regardlss of [....]/async if (index < 0 || index >= commands.Length || commands[index].Command != "QUIT\r\n") { throw; } } } return(PostReadCommandProcessing(ref stream)); }
private bool PostSendCommandProcessing(ref Stream stream) { if (_doRead) { // In async case, the next call can actually result in a // series of synchronous completions that eventually close // the connection. So we need to save the members that // we need to access, since they may not be valid after the // next call returns bool isAsync = _isAsync; int index = _index; PipelineEntry[] commands = _commands; try { ResponseDescription response = ReceiveCommandResponse(); if (isAsync) { return(true); } _currentResponseDescription = response; } catch { // If we get an exception on the QUIT command (which is // always the last command), ignore the final exception // and continue with the pipeline regardlss of sync/async if (index < 0 || index >= commands.Length || commands[index].Command != "QUIT\r\n") { throw; } } } return(PostReadCommandProcessing(ref stream)); }
/// <summary> /// This function is called a derived class to determine whether a response is valid, and when it is complete. /// </summary> protected virtual bool CheckValid(ResponseDescription response, ref int validThrough, ref int completeLength) { return(false); }
protected override bool CheckValid(ResponseDescription response, ref int validThrough, ref int completeLength) { if (response.StatusBuffer.Length >= 4) { string str = response.StatusBuffer.ToString(); if (response.Status == -1) { if ((!char.IsDigit(str[0]) || !char.IsDigit(str[1])) || (!char.IsDigit(str[2]) || ((str[3] != ' ') && (str[3] != '-')))) { return false; } response.StatusCodeString = str.Substring(0, 3); response.Status = Convert.ToInt16(response.StatusCodeString, NumberFormatInfo.InvariantInfo); if (str[3] == '-') { response.Multiline = true; } } int num = 0; while ((num = str.IndexOf("\r\n", validThrough)) != -1) { int startIndex = validThrough; validThrough = num + 2; if (!response.Multiline) { completeLength = validThrough; return true; } if (((str.Length > (startIndex + 4)) && (str.Substring(startIndex, 3) == response.StatusCodeString)) && (str[startIndex + 3] == ' ')) { completeLength = validThrough; return true; } } } return true; }
/// <summary> /// This function is implemented in a derived class to determine whether a response is valid, and when it is complete. /// </summary> protected virtual bool CheckValid(ResponseDescription response, ref int validThrough, ref int completeLength) { return false; }
private bool PostReadCommandProcessing(ref Stream stream) { if (_index >= _commands.Length) return false; // Set up front to prevent a race condition on result == PipelineInstruction.Pause _doSend = false; _doRead = false; PipelineInstruction result; PipelineEntry entry; if (_index == -1) entry = null; else entry = _commands[_index]; // Final QUIT command may get exceptions since the connection // may be already closed by the server. So there is no response // to process, just advance the pipeline to continue. if (_currentResponseDescription == null && entry.Command == "QUIT\r\n") result = PipelineInstruction.Advance; else result = PipelineCallback(entry, _currentResponseDescription, false, ref stream); if (result == PipelineInstruction.Abort) { Exception exception; if (_abortReason != string.Empty) exception = new WebException(_abortReason); else exception = GenerateException(SR.net_ftp_protocolerror, WebExceptionStatus.ServerProtocolViolation, null); Abort(exception); throw exception; } else if (result == PipelineInstruction.Advance) { _currentResponseDescription = null; _doSend = true; _doRead = true; _index++; } else if (result == PipelineInstruction.Pause) { // PipelineCallback did an async operation and will have to re-enter again. // Hold on for now. return true; } else if (result == PipelineInstruction.GiveStream) { // We will have another response coming, don't send _currentResponseDescription = null; _doRead = true; if (_isAsync) { // If they block in the requestcallback we should still continue the pipeline ContinueCommandPipeline(); InvokeRequestCallback(stream); } return true; } else if (result == PipelineInstruction.Reread) { // Another response is expected after this one _currentResponseDescription = null; _doRead = true; } return false; }
protected void InitCommandPipeline(WebRequest request, PipelineEntry[] commands, bool isAsync) { _commands = commands; _index = 0; _request = request; _aborted = false; _doRead = true; _doSend = true; _currentResponseDescription = null; _isAsync = isAsync; _recoverableFailure = false; _abortReason = string.Empty; }
// This is called by underlying base class code, each time a new response is received from the wire or a protocol stage is resumed. // This function controls the setting up of a data socket/connection, and of saving off the server responses. protected override PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Command:{entry?.Command} Description:{response?.StatusDescription}"); // null response is not expected if (response == null) return PipelineInstruction.Abort; FtpStatusCode status = (FtpStatusCode)response.Status; // // Update global "current status" for FtpWebRequest // if (status != FtpStatusCode.ClosingControl) { // A 221 status won't be reflected on the user FTP response // Anything else will (by design?) StatusCode = status; StatusLine = response.StatusDescription; } // If the status code is outside the range defined in RFC (1xx to 5xx) throw if (response.InvalidStatusCode) throw new WebException(SR.net_InvalidStatusCode, WebExceptionStatus.ProtocolError); // Update the banner message if any, this is a little hack because the "entry" param is null if (_index == -1) { if (status == FtpStatusCode.SendUserCommand) { _bannerMessage = new StringBuilder(); _bannerMessage.Append(StatusLine); return PipelineInstruction.Advance; } else if (status == FtpStatusCode.ServiceTemporarilyNotAvailable) { return PipelineInstruction.Reread; } else throw GenerateException(status, response.StatusDescription, null); } // // Check for the result of our attempt to use UTF8 // if (entry.Command == "OPTS utf8 on\r\n") { if (response.PositiveCompletion) { Encoding = Encoding.UTF8; } else { Encoding = Encoding.Default; } return PipelineInstruction.Advance; } // If we are already logged in and the server returns 530 then // the server does not support re-issuing a USER command, // tear down the connection and start all over again if (entry.Command.IndexOf("USER") != -1) { // The server may not require a password for this user, so bypass the password command if (status == FtpStatusCode.LoggedInProceed) { _loginState = FtpLoginState.LoggedIn; _index++; } } // // Throw on an error with possible recovery option // if (response.TransientFailure || response.PermanentFailure) { if (status == FtpStatusCode.ServiceNotAvailable) { MarkAsRecoverableFailure(); } throw GenerateException(status, response.StatusDescription, null); } if (_loginState != FtpLoginState.LoggedIn && entry.Command.IndexOf("PASS") != -1) { // Note the fact that we logged in if (status == FtpStatusCode.NeedLoginAccount || status == FtpStatusCode.LoggedInProceed) _loginState = FtpLoginState.LoggedIn; else throw GenerateException(status, response.StatusDescription, null); } // // Parse special cases // if (entry.HasFlag(PipelineEntryFlags.CreateDataConnection) && (response.PositiveCompletion || response.PositiveIntermediate)) { bool isSocketReady; PipelineInstruction result = QueueOrCreateDataConection(entry, response, timeout, ref stream, out isSocketReady); if (!isSocketReady) return result; // otheriwse we have a stream to create } // // This is part of the above case and it's all about giving data stream back // if (status == FtpStatusCode.OpeningData || status == FtpStatusCode.DataAlreadyOpen) { if (_dataSocket == null) { return PipelineInstruction.Abort; } if (!entry.HasFlag(PipelineEntryFlags.GiveDataStream)) { _abortReason = SR.Format(SR.net_ftp_invalid_status_response, status, entry.Command); return PipelineInstruction.Abort; } // Parse out the Content length, if we can TryUpdateContentLength(response.StatusDescription); // Parse out the file name, when it is returned and use it for our ResponseUri FtpWebRequest request = (FtpWebRequest)_request; if (request.MethodInfo.ShouldParseForResponseUri) { TryUpdateResponseUri(response.StatusDescription, request); } return QueueOrCreateFtpDataStream(ref stream); } // // Parse responses by status code exclusivelly // // Update welcome message if (status == FtpStatusCode.LoggedInProceed) { _welcomeMessage.Append(StatusLine); } // OR set the user response ExitMessage else if (status == FtpStatusCode.ClosingControl) { _exitMessage.Append(response.StatusDescription); // And close the control stream socket on "QUIT" CloseSocket(); } // OR set us up for SSL/TLS, after this we'll be writing securely else if (status == FtpStatusCode.ServerWantsSecureSession) { // If NetworkStream is a TlsStream, then this must be in the async callback // from completing the SSL handshake. // So just let the pipeline continue. if (!(NetworkStream is TlsStream)) { FtpWebRequest request = (FtpWebRequest)_request; TlsStream tlsStream = new TlsStream(NetworkStream, Socket, request.RequestUri.Host, request.ClientCertificates); if (_isAsync) { tlsStream.BeginAuthenticateAsClient(ar => { try { tlsStream.EndAuthenticateAsClient(ar); NetworkStream = tlsStream; this.ContinueCommandPipeline(); } catch (Exception e) { this.CloseSocket(); this.InvokeRequestCallback(e); } }, null); return PipelineInstruction.Pause; } else { tlsStream.AuthenticateAsClient(); NetworkStream = tlsStream; } } } // OR parse out the file size or file time, usually a result of sending SIZE/MDTM commands else if (status == FtpStatusCode.FileStatus) { FtpWebRequest request = (FtpWebRequest)_request; if (entry.Command.StartsWith("SIZE ")) { _contentLength = GetContentLengthFrom213Response(response.StatusDescription); } else if (entry.Command.StartsWith("MDTM ")) { _lastModified = GetLastModifiedFrom213Response(response.StatusDescription); } } // OR parse out our login directory else if (status == FtpStatusCode.PathnameCreated) { if (entry.Command == "PWD\r\n" && !entry.HasFlag(PipelineEntryFlags.UserCommand)) { _loginDirectory = GetLoginDirectory(response.StatusDescription); } } // Asserting we have some positive response else { // We only use CWD to reset ourselves back to the login directory. if (entry.Command.IndexOf("CWD") != -1) { _establishedServerDirectory = _requestedServerDirectory; } } // Intermediate responses require rereading if (response.PositiveIntermediate || (!UsingSecureStream && entry.Command == "AUTH TLS\r\n")) { return PipelineInstruction.Reread; } return PipelineInstruction.Advance; }
// private bool PostSendCommandProcessing(ref Stream stream) { /* ** I don;t see how this code can be still relevant, remove it of no problems observed ** // // This is a general race condition in Sync mode, if the server returns an error // after we open the data connection, we will be off reading the data connection, // and not the control connection. The best we can do is try to poll, and in the // the worst case, we will timeout on establishing the data connection. // if (!m_DoRead && !m_Async) { m_DoRead = Poll(100 * 1000, SelectMode.SelectRead); // Poll is in Microseconds. } */ if (m_DoRead) { // In async case, The next call can actually result in a // series of synchronous completions that eventually close // the connection. So we need to save the members that // we need to access, since they may not be valid after the // next call returns bool async = m_Async; int index = m_Index; PipelineEntry[] commands = m_Commands; try { ResponseDescription response = ReceiveCommandResponse(); if (async) { return true; } m_CurrentResponseDescription = response; } catch { // If we get an exception on the QUIT command (which is // always the last command), ignore the final exception // and continue with the pipeline regardlss of sync/async if (index < 0 || index >= commands.Length || commands[index].Command != "QUIT\r\n") throw; } } return PostReadCommandProcessing(ref stream); }
private bool PostReadCommandProcessing(ref Stream stream) { if (this.m_Index < this.m_Commands.Length) { PipelineInstruction advance; PipelineEntry entry; this.m_DoSend = false; this.m_DoRead = false; if (this.m_Index == -1) { entry = null; } else { entry = this.m_Commands[this.m_Index]; } if ((this.m_CurrentResponseDescription == null) && (entry.Command == "QUIT\r\n")) { advance = PipelineInstruction.Advance; } else { advance = this.PipelineCallback(entry, this.m_CurrentResponseDescription, false, ref stream); } switch (advance) { case PipelineInstruction.Advance: this.m_CurrentResponseDescription = null; this.m_DoSend = true; this.m_DoRead = true; this.m_Index++; break; case PipelineInstruction.Pause: return true; case PipelineInstruction.Abort: Exception exception; if (this.m_AbortReason != string.Empty) { exception = new WebException(this.m_AbortReason); } else { exception = this.GenerateException(WebExceptionStatus.ServerProtocolViolation, null); } this.Abort(exception); throw exception; case PipelineInstruction.GiveStream: this.m_CurrentResponseDescription = null; this.m_DoRead = true; if (this.m_Async) { this.ContinueCommandPipeline(); this.InvokeRequestCallback(stream); } return true; case PipelineInstruction.Reread: this.m_CurrentResponseDescription = null; this.m_DoRead = true; break; } } return false; }
private PipelineInstruction QueueOrCreateDataConection(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream, out bool isSocketReady) { isSocketReady = false; if (m_DataHandshakeStarted) { isSocketReady = true; return PipelineInstruction.Pause; //if we already started then this is re-entering into the callback where we proceed with the stream } m_DataHandshakeStarted = true; // handle passive responses by parsing the port and later doing a Connect(...) bool isPassive = false; int port = -1; if (entry.Command == "PASV\r\n" || entry.Command == "EPSV\r\n") { if (!response.PositiveCompletion) { m_AbortReason = SR.GetString(SR.net_ftp_server_failed_passive, response.Status); return PipelineInstruction.Abort; } if (entry.Command == "PASV\r\n") { port = GetPortV4(response.StatusDescription); } else { port = GetPortV6(response.StatusDescription); } isPassive = true; } new SocketPermission(PermissionState.Unrestricted).Assert(); try { if (isPassive) { GlobalLog.Assert(port != -1, "FtpControlStream#{0}|'port' not set.", ValidationHelper.HashString(this)); try { m_DataSocket = CreateFtpDataSocket((FtpWebRequest)m_Request, Socket); } catch (ObjectDisposedException) { throw ExceptionHelper.RequestAbortedException; } IPEndPoint localEndPoint = new IPEndPoint(((IPEndPoint)Socket.LocalEndPoint).Address, 0); m_DataSocket.Bind(localEndPoint); m_PassiveEndPoint = new IPEndPoint(ServerAddress, port); } PipelineInstruction result; if (m_PassiveEndPoint != null) { IPEndPoint passiveEndPoint = m_PassiveEndPoint; m_PassiveEndPoint = null; GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "starting Connect()"); if (m_Async) { m_DataSocket.BeginConnect(passiveEndPoint, m_ConnectCallbackDelegate, this); result = PipelineInstruction.Pause; } else { m_DataSocket.Connect(passiveEndPoint); result = PipelineInstruction.Advance; // for passive mode we end up going to the next command } } else { GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "starting Accept()"); if (m_Async) { m_DataSocket.BeginAccept(m_AcceptCallbackDelegate, this); result = PipelineInstruction.Pause; } else { Socket listenSocket = m_DataSocket; try { m_DataSocket = m_DataSocket.Accept(); if (!ServerAddress.Equals(((IPEndPoint)m_DataSocket.RemoteEndPoint).Address)) { m_DataSocket.Close(); throw new WebException(SR.GetString(SR.net_ftp_active_address_different), WebExceptionStatus.ProtocolError); } isSocketReady = true; // for active mode we end up creating a stream before advancing the pipeline result = PipelineInstruction.Pause; } finally { listenSocket.Close(); } } } return result; } finally { SocketPermission.RevertAssert(); } }
// // This is called by underlying base class code, each time a new response is received from the wire or a protocol stage is resumed. // This function controls the seting up of a data socket/connection, and of saving off the server responses // protected override PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream) { GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + ">" + (entry == null? "null" : entry.Command)); GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + ">" + ((response == null) ? "null" : response.StatusDescription)); // null response is not expected if (response == null) return PipelineInstruction.Abort; FtpStatusCode status = (FtpStatusCode) response.Status; // // Update global "current status" for FtpWebRequest // if (status != FtpStatusCode.ClosingControl) { // A 221 status won't be reflected on the user FTP response // Anything else will (by design?) StatusCode = status; StatusLine = response.StatusDescription; } // If the status code is outside the range defined in RFC (1xx to 5xx) throw if (response.InvalidStatusCode) throw new WebException(SR.GetString(SR.net_InvalidStatusCode), WebExceptionStatus.ProtocolError); // Update the banner message if any, this is a little hack because the "entry" param is null if (m_Index == -1) { if (status == FtpStatusCode.SendUserCommand) { m_BannerMessage = new StringBuilder(); m_BannerMessage.Append(StatusLine); return PipelineInstruction.Advance; } else if (status == FtpStatusCode.ServiceTemporarilyNotAvailable) { return PipelineInstruction.Reread; } else throw GenerateException(status,response.StatusDescription, null); } // // Check for the result of our attempt to use UTF8 // Condsider: optimize this for speed (avoid string compare) as that is the only command that may fail // if (entry.Command == "OPTS utf8 on\r\n") { if (response.PositiveCompletion) { Encoding = Encoding.UTF8; } else { Encoding = Encoding.Default; } return PipelineInstruction.Advance; } // If we are already logged in and the server returns 530 then // the server does not support re-issuing a USER command, // tear down the connection and start all over again if (entry.Command.IndexOf("USER") != -1) { // The server may not require a password for this user, so bypass the password command if (status == FtpStatusCode.LoggedInProceed) { m_LoginState = FtpLoginState.LoggedIn; m_Index++; } // The server does not like re-login // (We are logged in already but want to re-login under a different user) else if (status == FtpStatusCode.NotLoggedIn && m_LoginState != FtpLoginState.NotLoggedIn) { m_LoginState = FtpLoginState.ReloginFailed; throw ExceptionHelper.IsolatedException; } } // // Throw on an error with possible recovery option // if (response.TransientFailure || response.PermanentFailure) { if (status == FtpStatusCode.ServiceNotAvailable) { MarkAsRecoverableFailure(); } throw GenerateException(status,response.StatusDescription, null); } if (m_LoginState != FtpLoginState.LoggedIn && entry.Command.IndexOf("PASS") != -1) { // Note the fact that we logged in if (status == FtpStatusCode.NeedLoginAccount || status == FtpStatusCode.LoggedInProceed) m_LoginState = FtpLoginState.LoggedIn; else throw GenerateException(status,response.StatusDescription, null); } // // Parse special cases // if (entry.HasFlag(PipelineEntryFlags.CreateDataConnection) && (response.PositiveCompletion || response.PositiveIntermediate)) { bool isSocketReady; PipelineInstruction result = QueueOrCreateDataConection(entry, response, timeout, ref stream, out isSocketReady); if (!isSocketReady) return result; // otheriwse we have a stream to create } // // This is part of the above case and it's all about giving data stream back // if (status == FtpStatusCode.OpeningData || status == FtpStatusCode.DataAlreadyOpen) { if (m_DataSocket == null) { // a better diagnostic? return PipelineInstruction.Abort; } if (!entry.HasFlag(PipelineEntryFlags.GiveDataStream)) { m_AbortReason = SR.GetString(SR.net_ftp_invalid_status_response, status, entry.Command); return PipelineInstruction.Abort; } // Parse out the Content length, if we can TryUpdateContentLength(response.StatusDescription); // Parse out the file name, when it is returned and use it for our ResponseUri FtpWebRequest request = (FtpWebRequest) m_Request; if (request.MethodInfo.ShouldParseForResponseUri) { TryUpdateResponseUri(response.StatusDescription, request); } return QueueOrCreateFtpDataStream(ref stream); } // // Parse responses by status code exclusivelly // //Update welcome message if (status == FtpStatusCode.LoggedInProceed) { m_WelcomeMessage.Append(StatusLine); } // OR set the user response ExitMessage else if (status == FtpStatusCode.ClosingControl) { m_ExitMessage.Append(response.StatusDescription); // And close the control stream socket on "QUIT" CloseSocket(); } #if !FEATURE_PAL // OR set us up for SSL/TLS, after this we'll be writing securely else if (status == FtpStatusCode.ServerWantsSecureSession) { FtpWebRequest request = (FtpWebRequest) m_Request; TlsStream tlsStream = new TlsStream(request.RequestUri.Host, NetworkStream, request.ClientCertificates, Pool.ServicePoint, request, m_Async ? request.GetWritingContext().ContextCopy : null); NetworkStream = tlsStream; } #endif // !FEATURE_PAL // OR parse out the file size or file time, usually a result of sending SIZE/MDTM commands else if (status == FtpStatusCode.FileStatus) { FtpWebRequest request = (FtpWebRequest) m_Request; if (entry.Command.StartsWith("SIZE ")) { m_ContentLength = GetContentLengthFrom213Response(response.StatusDescription); } else if (entry.Command.StartsWith("MDTM ")) { m_LastModified = GetLastModifiedFrom213Response(response.StatusDescription); } } // OR parse out our login directory else if (status == FtpStatusCode.PathnameCreated) { if (entry.Command == "PWD\r\n" && !entry.HasFlag(PipelineEntryFlags.UserCommand)) { m_LoginDirectory = GetLoginDirectory(response.StatusDescription); } } // Asserting we have some positive response else { // We only use CWD to reset ourselves back to the login directory. if (entry.Command.IndexOf("CWD") != -1) { m_EstablishedServerDirectory = m_RequestedServerDirectory; } } // Intermediate responses require rereading if (response.PositiveIntermediate || (!UsingSecureStream && entry.Command == "AUTH TLS\r\n")) { return PipelineInstruction.Reread; } return PipelineInstruction.Advance; }
// // This is called by underlying base class code, each time a new response is received from the wire or a protocol stage is resumed. // This function controls the seting up of a data socket/connection, and of saving off the server responses // protected override PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream) { GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + ">" + (entry == null? "null" : entry.Command)); GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + ">" + ((response == null) ? "null" : response.StatusDescription)); // null response is not expected if (response == null) return PipelineInstruction.Abort; FtpStatusCode status = (FtpStatusCode) response.Status; // // Update global "current status" for FtpWebRequest // if (status != FtpStatusCode.ClosingControl) { // A 221 status won't be reflected on the user FTP response // Anything else will (by design?) StatusCode = status; StatusLine = response.StatusDescription; } // If the status code is outside the range defined in RFC (1xx to 5xx) throw if (response.InvalidStatusCode) throw new WebException(SR.GetString(SR.net_InvalidStatusCode), WebExceptionStatus.ProtocolError); if (m_Index == -1) { if (status == FtpStatusCode.SendUserCommand) { m_BannerMessage = new StringBuilder(); m_BannerMessage.Append(StatusLine); return PipelineInstruction.Advance; } else if (status == FtpStatusCode.ServiceTemporarilyNotAvailable) { return PipelineInstruction.Reread; } else throw GenerateException(status,response.StatusDescription, null); } // // Check for the result of our attempt to use UTF8 // Condsider: optimize this for speed (avoid string compare) as that is the only command that may fail // if (entry.Command == "OPTS utf8 on\r\n") { if (response.PositiveCompletion) { Encoding = Encoding.UTF8; } else { Encoding = Encoding.Default; } return PipelineInstruction.Advance; } // If we are already logged in and the server returns 530 then // the server does not support re-issuing a USER command, // tear down the connection and start all over again if (entry.Command.IndexOf("USER") != -1) { // The server may not require a password for this user, so bypass the password command if (status == FtpStatusCode.LoggedInProceed) { m_LoginState = FtpLoginState.LoggedIn; m_Index++; } // The server does not like re-login // (We are logged in already but want to re-login under a different user) else if (status == FtpStatusCode.NotLoggedIn && m_LoginState != FtpLoginState.NotLoggedIn) { m_LoginState = FtpLoginState.ReloginFailed; throw ExceptionHelper.IsolatedException; } } // // Throw on an error with possibe recovery option // if (response.TransientFailure || response.PermanentFailure) { if (status == FtpStatusCode.ServiceNotAvailable) { MarkAsRecoverableFailure(); } throw GenerateException(status,response.StatusDescription, null); } if (m_LoginState != FtpLoginState.LoggedIn && entry.Command.IndexOf("PASS") != -1) { // Note the fact that we logged in if (status == FtpStatusCode.NeedLoginAccount || status == FtpStatusCode.LoggedInProceed) m_LoginState = FtpLoginState.LoggedIn; else throw GenerateException(status,response.StatusDescription, null); } // // Parse special cases // if (entry.HasFlag(PipelineEntryFlags.CreateDataConnection) && (response.PositiveCompletion || response.PositiveIntermediate)) { bool isSocketReady; PipelineInstruction result = QueueOrCreateDataConection(entry, response, timeout, ref stream, out isSocketReady); if (!isSocketReady) return result; // otheriwse we have a stream to create } // // This is part of the above case and it's all about giving data stream back // if (status == FtpStatusCode.OpeningData || status == FtpStatusCode.DataAlreadyOpen) { if (m_DataSocket == null) { // a better diagnostic? return PipelineInstruction.Abort; } if (!entry.HasFlag(PipelineEntryFlags.GiveDataStream)) { m_AbortReason = SR.GetString(SR.net_ftp_invalid_status_response, status, entry.Command); return PipelineInstruction.Abort; } // Parse out the Content length, if we can TryUpdateContentLength(response.StatusDescription); // Parse out the file name, when it is returned and use it for our ResponseUri if (status == FtpStatusCode.OpeningData) { FtpWebRequest request = (FtpWebRequest) m_Request; if (request.MethodInfo.ShouldParseForResponseUri) { TryUpdateResponseUri(response.StatusDescription, request); } } return QueueOrCreateFtpDataStream(ref stream); } // // Parse responses by status code exclusivelly // //Update our command list if we have an alias if (status == FtpStatusCode.LoggedInProceed) { if(StatusLine.ToLower(CultureInfo.InvariantCulture).IndexOf("alias") > 0){ //find start of alias //skip first status code int i = StatusLine.IndexOf("230-",3); if(i > 0) { i+=4; //eat white space while(i<StatusLine.Length && StatusLine[i] == ' '){i++;} //not eol if (i <StatusLine.Length) { //get end of alias int j = StatusLine.IndexOf(' ',i); if(j<0) j=StatusLine.Length; m_Alias = StatusLine.Substring(i,j-i); if (!m_IsRootPath) { //update command list for (i=0;i<m_Commands.Length;i++){ if(m_Commands[i].Command.IndexOf("CWD") == 0) { string path = m_Alias+m_NewServerPath; m_Commands[i] = new PipelineEntry(FormatFtpCommand("CWD", path)); break; } } } } } } m_WelcomeMessage.Append(StatusLine); } // OR set the user response ExitMessage else if (status == FtpStatusCode.ClosingControl) { m_ExitMessage.Append(response.StatusDescription); // And close the control stream socket on "QUIT" CloseSocket(); } // OR parse out the file size or file time, usually a result of sending SIZE/MDTM commands else if (status == FtpStatusCode.FileStatus) { FtpWebRequest request = (FtpWebRequest) m_Request; if (entry.Command.StartsWith("SIZE ")) { m_ContentLength = GetContentLengthFrom213Response(response.StatusDescription); } else if (entry.Command.StartsWith("MDTM ")) { m_LastModified = GetLastModifiedFrom213Response(response.StatusDescription); } } // OR parse out our login directory else if (status == FtpStatusCode.PathnameCreated) { if (entry.Command == "PWD\r\n" && !entry.HasFlag(PipelineEntryFlags.UserCommand)) { m_LoginDirectory = GetLoginDirectory(response.StatusDescription); if (!m_IsRootPath && m_LoginDirectory != "\\" && m_LoginDirectory != "/" && m_Alias == null) { //update command list for (int i=0;i<m_Commands.Length;i++){ if(m_Commands[i].Command.IndexOf("CWD") == 0) { string path = m_LoginDirectory+m_NewServerPath; m_Commands[i] = new PipelineEntry(FormatFtpCommand("CWD", path)); break; } } } } } // Asserting we have some positive response else { // OR update the current path (optimize this pls) if (entry.Command.IndexOf("CWD") != -1) { m_PreviousServerPath = m_NewServerPath; } } // Intermidate responses require rereading if (response.PositiveIntermediate || (!UsingSecureStream && entry.Command == "AUTH TLS\r\n")) { return PipelineInstruction.Reread; } return PipelineInstruction.Advance; }
private CommandStream.PipelineInstruction QueueOrCreateDataConection(CommandStream.PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream, out bool isSocketReady) { CommandStream.PipelineInstruction instruction2; isSocketReady = false; if (this.m_DataHandshakeStarted) { isSocketReady = true; return CommandStream.PipelineInstruction.Pause; } this.m_DataHandshakeStarted = true; bool flag = false; int port = -1; if ((entry.Command == "PASV\r\n") || (entry.Command == "EPSV\r\n")) { if (!response.PositiveCompletion) { base.m_AbortReason = SR.GetString("net_ftp_server_failed_passive", new object[] { response.Status }); return CommandStream.PipelineInstruction.Abort; } if (entry.Command == "PASV\r\n") { port = this.GetPortV4(response.StatusDescription); } else { port = this.GetPortV6(response.StatusDescription); } flag = true; } new SocketPermission(PermissionState.Unrestricted).Assert(); try { CommandStream.PipelineInstruction pause; if (flag) { try { this.m_DataSocket = this.CreateFtpDataSocket((FtpWebRequest) base.m_Request, base.Socket); } catch (ObjectDisposedException) { throw ExceptionHelper.RequestAbortedException; } this.m_PassiveEndPoint = new IPEndPoint(base.ServerAddress, port); } if (this.m_PassiveEndPoint != null) { IPEndPoint passiveEndPoint = this.m_PassiveEndPoint; this.m_PassiveEndPoint = null; if (base.m_Async) { this.m_DataSocket.BeginConnect(passiveEndPoint, m_ConnectCallbackDelegate, this); pause = CommandStream.PipelineInstruction.Pause; } else { this.m_DataSocket.Connect(passiveEndPoint); pause = CommandStream.PipelineInstruction.Advance; } } else if (base.m_Async) { this.m_DataSocket.BeginAccept(m_AcceptCallbackDelegate, this); pause = CommandStream.PipelineInstruction.Pause; } else { Socket dataSocket = this.m_DataSocket; try { this.m_DataSocket = this.m_DataSocket.Accept(); if (!base.ServerAddress.Equals(((IPEndPoint) this.m_DataSocket.RemoteEndPoint).Address)) { this.m_DataSocket.Close(); throw new WebException(SR.GetString("net_ftp_active_address_different"), WebExceptionStatus.ProtocolError); } isSocketReady = true; pause = CommandStream.PipelineInstruction.Pause; } finally { dataSocket.Close(); } } instruction2 = pause; } finally { CodeAccessPermission.RevertAssert(); } return instruction2; }
protected override CommandStream.PipelineInstruction PipelineCallback(CommandStream.PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream) { if (response == null) { return CommandStream.PipelineInstruction.Abort; } FtpStatusCode status = (FtpStatusCode) response.Status; if (status != FtpStatusCode.ClosingControl) { this.StatusCode = status; this.StatusLine = response.StatusDescription; } if (response.InvalidStatusCode) { throw new WebException(SR.GetString("net_InvalidStatusCode"), WebExceptionStatus.ProtocolError); } if (base.m_Index == -1) { switch (status) { case FtpStatusCode.SendUserCommand: this.m_BannerMessage = new StringBuilder(); this.m_BannerMessage.Append(this.StatusLine); return CommandStream.PipelineInstruction.Advance; case FtpStatusCode.ServiceTemporarilyNotAvailable: return CommandStream.PipelineInstruction.Reread; } throw base.GenerateException(status, response.StatusDescription, null); } if (entry.Command == "OPTS utf8 on\r\n") { if (response.PositiveCompletion) { base.Encoding = Encoding.UTF8; } else { base.Encoding = Encoding.Default; } return CommandStream.PipelineInstruction.Advance; } if (entry.Command.IndexOf("USER") != -1) { if (status == FtpStatusCode.LoggedInProceed) { this.m_LoginState = FtpLoginState.LoggedIn; base.m_Index++; } else if ((status == FtpStatusCode.NotLoggedIn) && (this.m_LoginState != FtpLoginState.NotLoggedIn)) { this.m_LoginState = FtpLoginState.ReloginFailed; throw ExceptionHelper.IsolatedException; } } if (response.TransientFailure || response.PermanentFailure) { if (status == FtpStatusCode.ServiceNotAvailable) { base.MarkAsRecoverableFailure(); } throw base.GenerateException(status, response.StatusDescription, null); } if ((this.m_LoginState != FtpLoginState.LoggedIn) && (entry.Command.IndexOf("PASS") != -1)) { switch (status) { case FtpStatusCode.NeedLoginAccount: case FtpStatusCode.LoggedInProceed: this.m_LoginState = FtpLoginState.LoggedIn; goto Label_017A; } throw base.GenerateException(status, response.StatusDescription, null); } Label_017A: if (entry.HasFlag(CommandStream.PipelineEntryFlags.CreateDataConnection) && (response.PositiveCompletion || response.PositiveIntermediate)) { bool flag; CommandStream.PipelineInstruction instruction = this.QueueOrCreateDataConection(entry, response, timeout, ref stream, out flag); if (!flag) { return instruction; } } switch (status) { case FtpStatusCode.OpeningData: case FtpStatusCode.DataAlreadyOpen: { if (this.m_DataSocket == null) { return CommandStream.PipelineInstruction.Abort; } if (!entry.HasFlag(CommandStream.PipelineEntryFlags.GiveDataStream)) { base.m_AbortReason = SR.GetString("net_ftp_invalid_status_response", new object[] { status, entry.Command }); return CommandStream.PipelineInstruction.Abort; } this.TryUpdateContentLength(response.StatusDescription); FtpWebRequest request = (FtpWebRequest) base.m_Request; if (request.MethodInfo.ShouldParseForResponseUri) { this.TryUpdateResponseUri(response.StatusDescription, request); } return this.QueueOrCreateFtpDataStream(ref stream); } case FtpStatusCode.LoggedInProceed: this.m_WelcomeMessage.Append(this.StatusLine); break; case FtpStatusCode.ClosingControl: this.m_ExitMessage.Append(response.StatusDescription); base.CloseSocket(); break; case FtpStatusCode.ServerWantsSecureSession: { FtpWebRequest initiatingRequest = (FtpWebRequest) base.m_Request; TlsStream stream2 = new TlsStream(initiatingRequest.RequestUri.Host, base.NetworkStream, initiatingRequest.ClientCertificates, base.Pool.ServicePoint, initiatingRequest, base.m_Async ? initiatingRequest.GetWritingContext().ContextCopy : null); base.NetworkStream = stream2; break; } case FtpStatusCode.FileStatus: { FtpWebRequest request1 = (FtpWebRequest) base.m_Request; if (entry.Command.StartsWith("SIZE ")) { this.m_ContentLength = this.GetContentLengthFrom213Response(response.StatusDescription); } else if (entry.Command.StartsWith("MDTM ")) { this.m_LastModified = this.GetLastModifiedFrom213Response(response.StatusDescription); } break; } default: if (status == FtpStatusCode.PathnameCreated) { if ((entry.Command == "PWD\r\n") && !entry.HasFlag(CommandStream.PipelineEntryFlags.UserCommand)) { this.m_LoginDirectory = this.GetLoginDirectory(response.StatusDescription); } } else if (entry.Command.IndexOf("CWD") != -1) { this.m_EstablishedServerDirectory = this.m_RequestedServerDirectory; } break; } if (!response.PositiveIntermediate && (base.UsingSecureStream || !(entry.Command == "AUTH TLS\r\n"))) { return CommandStream.PipelineInstruction.Advance; } return CommandStream.PipelineInstruction.Reread; }
/// <summary> /// ReceiveCommandResponseCallback is the main "while loop" of the ReceiveCommandResponse function family. /// In general, what is does is perform an EndReceive() to complete the previous retrieval of bytes from the /// server (unless it is using a buffered response) It then processes what is received by using the /// implementing class's CheckValid() function, as described above. If the response is complete, it returns the single complete /// response in the GeneralResponseDescription created in BeginReceiveComamndResponse, and buffers the rest as described above. /// /// If the resposne is not complete, it issues another Connection.BeginReceive, with callback ReceiveCommandResponse2, /// so the action will continue at the next invocation of ReceiveCommandResponse2. /// </summary> /// <param name="asyncResult"></param> /// private void ReceiveCommandResponseCallback(ReceiveState state, int bytesRead) { // completeLength will be set to a nonnegative number by CheckValid if the response is complete: // it will set completeLength to the length of a complete response. int completeLength = -1; while (true) { int validThrough = state.ValidThrough; // passed to checkvalid // If we have a Buffered response (ie data was received with the last response that was past the end of that response) // deal with it as if we had just received it now instead of actually doing another receive if (m_Buffer.Length > 0) { // Append the string we got from the buffer, and flush it out. state.Resp.StatusBuffer.Append(m_Buffer); m_Buffer = string.Empty; // invoke checkvalid. if (!CheckValid(state.Resp, ref validThrough, ref completeLength)) { throw GenerateException(WebExceptionStatus.ServerProtocolViolation, null); } } else // we did a Connection.BeginReceive. Note that in this case, all bytes received are in the receive buffer (because bytes from // the buffer were transferred there if necessary { // this indicates the connection was closed. if (bytesRead <= 0) { throw GenerateException(WebExceptionStatus.ServerProtocolViolation, null); } // decode the bytes in the receive buffer into a string, append it to the statusbuffer, and invoke checkvalid. // Decoder automatically takes care of caching partial codepoints at the end of a buffer. char[] chars = new char[m_Decoder.GetCharCount(state.Buffer, 0, bytesRead)]; int numChars = m_Decoder.GetChars(state.Buffer, 0, bytesRead, chars, 0, false); string szResponse = new string(chars, 0, numChars); state.Resp.StatusBuffer.Append(szResponse); if (!CheckValid(state.Resp, ref validThrough, ref completeLength)) { throw GenerateException(WebExceptionStatus.ServerProtocolViolation, null); } // If the response is complete, then determine how many characters are left over...these bytes need to be set into Buffer. if (completeLength >= 0) { int unusedChars = state.Resp.StatusBuffer.Length - completeLength; if (unusedChars > 0) { m_Buffer = szResponse.Substring(szResponse.Length - unusedChars, unusedChars); } } } // Now, in general, if the response is not complete, update the "valid through" length for the efficiency of checkValid. // and perform the next receive. // Note that there may NOT be bytes in the beginning of the receive buffer (even if there were partial characters left over after the // last encoding), because they get tracked in the Decoder. if (completeLength < 0) { state.ValidThrough = validThrough; try { if (m_Async) { BeginRead(state.Buffer, 0, state.Buffer.Length, m_ReadCallbackDelegate, state); return; } else { bytesRead = Read(state.Buffer, 0, state.Buffer.Length); if (bytesRead == 0) { CloseSocket(); } continue; } } catch (IOException) { MarkAsRecoverableFailure(); throw; } catch { throw; } } // the response is completed break; } // Otherwise, we have a complete response. string responseString = state.Resp.StatusBuffer.ToString(); state.Resp.StatusDescription = responseString.Substring(0, completeLength); // set the StatusDescription to the complete part of the response. Note that the Buffer has already been taken care of above. if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_received_response, responseString.Substring(0, completeLength - 2))); } if (m_Async) { // Tell who is listening what was received. if (state.Resp != null) { m_CurrentResponseDescription = state.Resp; } Stream stream = null; if (PostReadCommandProcessing(ref stream)) { return; } ContinueCommandPipeline(); } }
protected void InitCommandPipeline(WebRequest request, PipelineEntry [] commands, bool async) { m_Commands = commands; m_Index = 0; m_Request = request; m_Aborted = false; m_DoRead = true; m_DoSend = true; m_CurrentResponseDescription = null; m_Async = async; m_RecoverableFailure = false; m_AbortReason = string.Empty; }
private bool PostSendCommandProcessing(ref Stream stream) { if (this.m_DoRead) { bool async = this.m_Async; int index = this.m_Index; PipelineEntry[] commands = this.m_Commands; try { ResponseDescription description = this.ReceiveCommandResponse(); if (async) { return true; } this.m_CurrentResponseDescription = description; } catch { if (((index < 0) || (index >= commands.Length)) || (commands[index].Command != "QUIT\r\n")) { throw; } } } return this.PostReadCommandProcessing(ref stream); }
protected override bool CheckValid(ResponseDescription response, ref int validThrough, ref int completeLength) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"CheckValid({response.StatusBuffer})"); // If the response is less than 4 bytes long, it is too short to tell, so return true, valid so far. if (response.StatusBuffer.Length < 4) { return true; } string responseString = response.StatusBuffer.ToString(); // Otherwise, if there is no status code for this response yet, get one. if (response.Status == ResponseDescription.NoStatus) { // If the response does not start with three digits, then it is not a valid response from an FTP server. if (!(Char.IsDigit(responseString[0]) && Char.IsDigit(responseString[1]) && Char.IsDigit(responseString[2]) && (responseString[3] == ' ' || responseString[3] == '-'))) { return false; } else { response.StatusCodeString = responseString.Substring(0, 3); response.Status = Convert.ToInt16(response.StatusCodeString, NumberFormatInfo.InvariantInfo); } // IF a hyphen follows the status code on the first line of the response, then we have a multiline response coming. if (responseString[3] == '-') { response.Multiline = true; } } // If a complete line of response has been received from the server, then see if the // overall response is complete. // If this was not a multiline response, then the response is complete at the end of the line. // If this was a multiline response (indicated by three digits followed by a '-' in the first line), // then we see if the last line received started with the same three digits followed by a space. // If it did, then this is the sign of a complete multiline response. // If the line contained three other digits followed by the response, then this is a violation of the // FTP protocol for multiline responses. // All other cases indicate that the response is not yet complete. int index = 0; while ((index = responseString.IndexOf("\r\n", validThrough)) != -1) // gets the end line. { int lineStart = validThrough; validThrough = index + 2; // validThrough now marks the end of the line being examined. if (!response.Multiline) { completeLength = validThrough; return true; } if (responseString.Length > lineStart + 4) { // If the first three characters of the the response line currently being examined // match the status code, then if they are followed by a space, then we // have reached the end of the reply. if (responseString.Substring(lineStart, 3) == response.StatusCodeString) { if (responseString[lineStart + 3] == ' ') { completeLength = validThrough; return true; } } } } return true; }
private void ReceiveCommandResponseCallback(ReceiveState state, int bytesRead) { int num2; int completeLength = -1; Label_0002: num2 = state.ValidThrough; if (this.m_Buffer.Length > 0) { state.Resp.StatusBuffer.Append(this.m_Buffer); this.m_Buffer = string.Empty; if (!this.CheckValid(state.Resp, ref num2, ref completeLength)) { throw this.GenerateException(WebExceptionStatus.ServerProtocolViolation, null); } } else { if (bytesRead <= 0) { throw this.GenerateException(WebExceptionStatus.ServerProtocolViolation, null); } char[] chars = new char[this.m_Decoder.GetCharCount(state.Buffer, 0, bytesRead)]; int length = this.m_Decoder.GetChars(state.Buffer, 0, bytesRead, chars, 0, false); string str = new string(chars, 0, length); state.Resp.StatusBuffer.Append(str); if (!this.CheckValid(state.Resp, ref num2, ref completeLength)) { throw this.GenerateException(WebExceptionStatus.ServerProtocolViolation, null); } if (completeLength >= 0) { int num4 = state.Resp.StatusBuffer.Length - completeLength; if (num4 > 0) { this.m_Buffer = str.Substring(str.Length - num4, num4); } } } if (completeLength < 0) { state.ValidThrough = num2; try { if (this.m_Async) { this.BeginRead(state.Buffer, 0, state.Buffer.Length, m_ReadCallbackDelegate, state); return; } bytesRead = this.Read(state.Buffer, 0, state.Buffer.Length); if (bytesRead == 0) { base.CloseSocket(); } goto Label_0002; } catch (IOException) { this.MarkAsRecoverableFailure(); throw; } catch { throw; } } string str2 = state.Resp.StatusBuffer.ToString(); state.Resp.StatusDescription = str2.Substring(0, completeLength); if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.GetString("net_log_received_response", new object[] { str2.Substring(0, completeLength - 2) })); } if (this.m_Async) { if (state.Resp != null) { this.m_CurrentResponseDescription = state.Resp; } Stream stream = null; if (!this.PostReadCommandProcessing(ref stream)) { this.ContinueCommandPipeline(); } } }
private PipelineInstruction QueueOrCreateDataConection(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream, out bool isSocketReady) { isSocketReady = false; if (_dataHandshakeStarted) { isSocketReady = true; return PipelineInstruction.Pause; //if we already started then this is re-entering into the callback where we proceed with the stream } _dataHandshakeStarted = true; // Handle passive responses by parsing the port and later doing a Connect(...) bool isPassive = false; int port = -1; if (entry.Command == "PASV\r\n" || entry.Command == "EPSV\r\n") { if (!response.PositiveCompletion) { _abortReason = SR.Format(SR.net_ftp_server_failed_passive, response.Status); return PipelineInstruction.Abort; } if (entry.Command == "PASV\r\n") { port = GetPortV4(response.StatusDescription); } else { port = GetPortV6(response.StatusDescription); } isPassive = true; } if (isPassive) { if (port == -1) { NetEventSource.Fail(this, "'port' not set."); } try { _dataSocket = CreateFtpDataSocket((FtpWebRequest)_request, Socket); } catch (ObjectDisposedException) { throw ExceptionHelper.RequestAbortedException; } IPEndPoint localEndPoint = new IPEndPoint(((IPEndPoint)Socket.LocalEndPoint).Address, 0); _dataSocket.Bind(localEndPoint); _passiveEndPoint = new IPEndPoint(ServerAddress, port); } PipelineInstruction result; if (_passiveEndPoint != null) { IPEndPoint passiveEndPoint = _passiveEndPoint; _passiveEndPoint = null; if (NetEventSource.IsEnabled) NetEventSource.Info(this, "starting Connect()"); if (_isAsync) { _dataSocket.BeginConnect(passiveEndPoint, s_connectCallbackDelegate, this); result = PipelineInstruction.Pause; } else { _dataSocket.Connect(passiveEndPoint); result = PipelineInstruction.Advance; // for passive mode we end up going to the next command } } else { if (NetEventSource.IsEnabled) NetEventSource.Info(this, "starting Accept()"); if (_isAsync) { _dataSocket.BeginAccept(s_acceptCallbackDelegate, this); result = PipelineInstruction.Pause; } else { Socket listenSocket = _dataSocket; try { _dataSocket = _dataSocket.Accept(); if (!ServerAddress.Equals(((IPEndPoint)_dataSocket.RemoteEndPoint).Address)) { _dataSocket.Close(); throw new WebException(SR.net_ftp_active_address_different, WebExceptionStatus.ProtocolError); } isSocketReady = true; // for active mode we end up creating a stream before advancing the pipeline result = PipelineInstruction.Pause; } finally { listenSocket.Close(); } } } return result; }
private bool PostReadCommandProcessing(ref Stream stream) { if (this.m_Index < this.m_Commands.Length) { PipelineInstruction advance; PipelineEntry entry; this.m_DoSend = false; this.m_DoRead = false; if (this.m_Index == -1) { entry = null; } else { entry = this.m_Commands[this.m_Index]; } if ((this.m_CurrentResponseDescription == null) && (entry.Command == "QUIT\r\n")) { advance = PipelineInstruction.Advance; } else { advance = this.PipelineCallback(entry, this.m_CurrentResponseDescription, false, ref stream); } switch (advance) { case PipelineInstruction.Advance: this.m_CurrentResponseDescription = null; this.m_DoSend = true; this.m_DoRead = true; this.m_Index++; break; case PipelineInstruction.Pause: return(true); case PipelineInstruction.Abort: Exception exception; if (this.m_AbortReason != string.Empty) { exception = new WebException(this.m_AbortReason); } else { exception = this.GenerateException(WebExceptionStatus.ServerProtocolViolation, null); } this.Abort(exception); throw exception; case PipelineInstruction.GiveStream: this.m_CurrentResponseDescription = null; this.m_DoRead = true; if (this.m_Async) { this.ContinueCommandPipeline(); this.InvokeRequestCallback(stream); } return(true); case PipelineInstruction.Reread: this.m_CurrentResponseDescription = null; this.m_DoRead = true; break; } } return(false); }
private bool PostSendCommandProcessing(ref Stream stream) { if (_doRead) { // In async case, the next call can actually result in a // series of synchronous completions that eventually close // the connection. So we need to save the members that // we need to access, since they may not be valid after the // next call returns bool isAsync = _isAsync; int index = _index; PipelineEntry[] commands = _commands; try { ResponseDescription response = ReceiveCommandResponse(); if (isAsync) { return true; } _currentResponseDescription = response; } catch { // If we get an exception on the QUIT command (which is // always the last command), ignore the final exception // and continue with the pipeline regardlss of sync/async if (index < 0 || index >= commands.Length || commands[index].Command != "QUIT\r\n") throw; } } return PostReadCommandProcessing(ref stream); }
internal ReceiveState(CommandStream connection) { Connection = connection; Resp = new ResponseDescription(); Buffer = new byte[bufferSize]; //1024 ValidThrough = 0; }
protected virtual PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream) { return PipelineInstruction.Abort; }
// This is called by underlying base class code, each time a new response is received from the wire or a protocol stage is resumed. // This function controls the setting up of a data socket/connection, and of saving off the server responses. protected override PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"Command:{entry?.Command} Description:{response?.StatusDescription}"); } // null response is not expected if (response == null) { return(PipelineInstruction.Abort); } FtpStatusCode status = (FtpStatusCode)response.Status; // // Update global "current status" for FtpWebRequest // if (status != FtpStatusCode.ClosingControl) { // A 221 status won't be reflected on the user FTP response // Anything else will (by design?) StatusCode = status; StatusLine = response.StatusDescription; } // If the status code is outside the range defined in RFC (1xx to 5xx) throw if (response.InvalidStatusCode) { throw new WebException(SR.net_InvalidStatusCode, WebExceptionStatus.ProtocolError); } // Update the banner message if any, this is a little hack because the "entry" param is null if (_index == -1) { if (status == FtpStatusCode.SendUserCommand) { _bannerMessage = new StringBuilder(); _bannerMessage.Append(StatusLine); return(PipelineInstruction.Advance); } else if (status == FtpStatusCode.ServiceTemporarilyNotAvailable) { return(PipelineInstruction.Reread); } else { throw GenerateException(status, response.StatusDescription, null); } } // // Check for the result of our attempt to use UTF8 // if (entry.Command == "OPTS utf8 on\r\n") { if (response.PositiveCompletion) { Encoding = Encoding.UTF8; } else { Encoding = Encoding.Default; } return(PipelineInstruction.Advance); } // If we are already logged in and the server returns 530 then // the server does not support re-issuing a USER command, // tear down the connection and start all over again if (entry.Command.IndexOf("USER") != -1) { // The server may not require a password for this user, so bypass the password command if (status == FtpStatusCode.LoggedInProceed) { _loginState = FtpLoginState.LoggedIn; _index++; } } // // Throw on an error with possible recovery option // if (response.TransientFailure || response.PermanentFailure) { if (status == FtpStatusCode.ServiceNotAvailable) { MarkAsRecoverableFailure(); } throw GenerateException(status, response.StatusDescription, null); } if (_loginState != FtpLoginState.LoggedIn && entry.Command.IndexOf("PASS") != -1) { // Note the fact that we logged in if (status == FtpStatusCode.NeedLoginAccount || status == FtpStatusCode.LoggedInProceed) { _loginState = FtpLoginState.LoggedIn; } else { throw GenerateException(status, response.StatusDescription, null); } } // // Parse special cases // if (entry.HasFlag(PipelineEntryFlags.CreateDataConnection) && (response.PositiveCompletion || response.PositiveIntermediate)) { bool isSocketReady; PipelineInstruction result = QueueOrCreateDataConection(entry, response, timeout, ref stream, out isSocketReady); if (!isSocketReady) { return(result); } // otherwise we have a stream to create } // // This is part of the above case and it's all about giving data stream back // if (status == FtpStatusCode.OpeningData || status == FtpStatusCode.DataAlreadyOpen) { if (_dataSocket == null) { return(PipelineInstruction.Abort); } if (!entry.HasFlag(PipelineEntryFlags.GiveDataStream)) { _abortReason = SR.Format(SR.net_ftp_invalid_status_response, status, entry.Command); return(PipelineInstruction.Abort); } // Parse out the Content length, if we can TryUpdateContentLength(response.StatusDescription); // Parse out the file name, when it is returned and use it for our ResponseUri FtpWebRequest request = (FtpWebRequest)_request; if (request.MethodInfo.ShouldParseForResponseUri) { TryUpdateResponseUri(response.StatusDescription, request); } return(QueueOrCreateFtpDataStream(ref stream)); } // // Parse responses by status code exclusivelly // // Update welcome message if (status == FtpStatusCode.LoggedInProceed) { _welcomeMessage.Append(StatusLine); } // OR set the user response ExitMessage else if (status == FtpStatusCode.ClosingControl) { _exitMessage.Append(response.StatusDescription); // And close the control stream socket on "QUIT" CloseSocket(); } // OR set us up for SSL/TLS, after this we'll be writing securely else if (status == FtpStatusCode.ServerWantsSecureSession) { // If NetworkStream is a TlsStream, then this must be in the async callback // from completing the SSL handshake. // So just let the pipeline continue. if (!(NetworkStream is TlsStream)) { FtpWebRequest request = (FtpWebRequest)_request; TlsStream tlsStream = new TlsStream(NetworkStream, Socket, request.RequestUri.Host, request.ClientCertificates); if (_isAsync) { tlsStream.BeginAuthenticateAsClient(ar => { try { tlsStream.EndAuthenticateAsClient(ar); NetworkStream = tlsStream; this.ContinueCommandPipeline(); } catch (Exception e) { this.CloseSocket(); this.InvokeRequestCallback(e); } }, null); return(PipelineInstruction.Pause); } else { tlsStream.AuthenticateAsClient(); NetworkStream = tlsStream; } } } // OR parse out the file size or file time, usually a result of sending SIZE/MDTM commands else if (status == FtpStatusCode.FileStatus) { FtpWebRequest request = (FtpWebRequest)_request; if (entry.Command.StartsWith("SIZE ")) { _contentLength = GetContentLengthFrom213Response(response.StatusDescription); } else if (entry.Command.StartsWith("MDTM ")) { _lastModified = GetLastModifiedFrom213Response(response.StatusDescription); } } // OR parse out our login directory else if (status == FtpStatusCode.PathnameCreated) { if (entry.Command == "PWD\r\n" && !entry.HasFlag(PipelineEntryFlags.UserCommand)) { _loginDirectory = GetLoginDirectory(response.StatusDescription); } } // Asserting we have some positive response else { // We only use CWD to reset ourselves back to the login directory. if (entry.Command.IndexOf("CWD") != -1) { _establishedServerDirectory = _requestedServerDirectory; } } // Intermediate responses require rereading if (response.PositiveIntermediate || (!UsingSecureStream && entry.Command == "AUTH TLS\r\n")) { return(PipelineInstruction.Reread); } return(PipelineInstruction.Advance); }
/// <summary> /// ReceiveCommandResponseCallback is the main "while loop" of the ReceiveCommandResponse function family. /// In general, what is does is perform an EndReceive() to complete the previous retrieval of bytes from the /// server (unless it is using a buffered response) It then processes what is received by using the /// implementing class's CheckValid() function, as described above. If the response is complete, it returns the single complete /// response in the GeneralResponseDescription created in BeginReceiveComamndResponse, and buffers the rest as described above. /// /// If the response is not complete, it issues another Connection.BeginReceive, with callback ReceiveCommandResponse2, /// so the action will continue at the next invocation of ReceiveCommandResponse2. /// </summary> private void ReceiveCommandResponseCallback(ReceiveState state, int bytesRead) { // completeLength will be set to a nonnegative number by CheckValid if the response is complete: // it will set completeLength to the length of a complete response. int completeLength = -1; while (true) { int validThrough = state.ValidThrough; // passed to checkvalid // If we have a Buffered response (ie data was received with the last response that was past the end of that response) // deal with it as if we had just received it now instead of actually doing another receive if (_buffer.Length > 0) { // Append the string we got from the buffer, and flush it out. state.Resp.StatusBuffer.Append(_buffer); _buffer = string.Empty; // invoke checkvalid. if (!CheckValid(state.Resp, ref validThrough, ref completeLength)) { throw GenerateException(SR.net_ftp_protocolerror, WebExceptionStatus.ServerProtocolViolation, null); } } else // we did a Connection.BeginReceive. Note that in this case, all bytes received are in the receive buffer (because bytes from // the buffer were transferred there if necessary { // this indicates the connection was closed. if (bytesRead <= 0) { throw GenerateException(SR.net_ftp_protocolerror, WebExceptionStatus.ServerProtocolViolation, null); } // decode the bytes in the receive buffer into a string, append it to the statusbuffer, and invoke checkvalid. // Decoder automatically takes care of caching partial codepoints at the end of a buffer. char[] chars = new char[_decoder.GetCharCount(state.Buffer, 0, bytesRead)]; int numChars = _decoder.GetChars(state.Buffer, 0, bytesRead, chars, 0, false); string szResponse = new string(chars, 0, numChars); state.Resp.StatusBuffer.Append(szResponse); if (!CheckValid(state.Resp, ref validThrough, ref completeLength)) { throw GenerateException(SR.net_ftp_protocolerror, WebExceptionStatus.ServerProtocolViolation, null); } // If the response is complete, then determine how many characters are left over...these bytes need to be set into Buffer. if (completeLength >= 0) { int unusedChars = state.Resp.StatusBuffer.Length - completeLength; if (unusedChars > 0) { _buffer = szResponse.Substring(szResponse.Length - unusedChars, unusedChars); } } } // Now, in general, if the response is not complete, update the "valid through" length for the efficiency of checkValid, // and perform the next receive. // Note that there may NOT be bytes in the beginning of the receive buffer (even if there were partial characters left over after the // last encoding), because they get tracked in the Decoder. if (completeLength < 0) { state.ValidThrough = validThrough; try { if (_isAsync) { BeginRead(state.Buffer, 0, state.Buffer.Length, s_readCallbackDelegate, state); return; } else { bytesRead = Read(state.Buffer, 0, state.Buffer.Length); if (bytesRead == 0) CloseSocket(); continue; } } catch (IOException) { MarkAsRecoverableFailure(); throw; } catch { throw; } } // The response is completed break; } // Otherwise, we have a complete response. string responseString = state.Resp.StatusBuffer.ToString(); state.Resp.StatusDescription = responseString.Substring(0, completeLength); // Set the StatusDescription to the complete part of the response. Note that the Buffer has already been taken care of above. if (NetEventSource.Log.IsEnabled()) NetEventSource.PrintInfo(NetEventSource.ComponentType.Web, this, string.Format("Received response: {0}", responseString.Substring(0, completeLength - 2))); if (_isAsync) { // Tell who is listening what was received. if (state.Resp != null) { _currentResponseDescription = state.Resp; } Stream stream = null; if (PostReadCommandProcessing(ref stream)) return; ContinueCommandPipeline(); } }
private void ReceiveCommandResponseCallback(ReceiveState state, int bytesRead) { int num2; int completeLength = -1; Label_0002: num2 = state.ValidThrough; if (this.m_Buffer.Length > 0) { state.Resp.StatusBuffer.Append(this.m_Buffer); this.m_Buffer = string.Empty; if (!this.CheckValid(state.Resp, ref num2, ref completeLength)) { throw this.GenerateException(WebExceptionStatus.ServerProtocolViolation, null); } } else { if (bytesRead <= 0) { throw this.GenerateException(WebExceptionStatus.ServerProtocolViolation, null); } char[] chars = new char[this.m_Decoder.GetCharCount(state.Buffer, 0, bytesRead)]; int length = this.m_Decoder.GetChars(state.Buffer, 0, bytesRead, chars, 0, false); string str = new string(chars, 0, length); state.Resp.StatusBuffer.Append(str); if (!this.CheckValid(state.Resp, ref num2, ref completeLength)) { throw this.GenerateException(WebExceptionStatus.ServerProtocolViolation, null); } if (completeLength >= 0) { int num4 = state.Resp.StatusBuffer.Length - completeLength; if (num4 > 0) { this.m_Buffer = str.Substring(str.Length - num4, num4); } } } if (completeLength < 0) { state.ValidThrough = num2; try { if (this.m_Async) { this.BeginRead(state.Buffer, 0, state.Buffer.Length, m_ReadCallbackDelegate, state); return; } bytesRead = this.Read(state.Buffer, 0, state.Buffer.Length); if (bytesRead == 0) { base.CloseSocket(); } goto Label_0002; } catch (IOException) { this.MarkAsRecoverableFailure(); throw; } catch { throw; } } string str2 = state.Resp.StatusBuffer.ToString(); state.Resp.StatusDescription = str2.Substring(0, completeLength); if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.GetString("net_log_received_response", new object[] { str2.Substring(0, completeLength - 2) })); } if (this.m_Async) { if (state.Resp != null) { this.m_CurrentResponseDescription = state.Resp; } Stream stream = null; if (!this.PostReadCommandProcessing(ref stream)) { this.ContinueCommandPipeline(); } } }
protected override bool CheckValid(ResponseDescription response, ref int validThrough, ref int completeLength) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"CheckValid({response.StatusBuffer})"); } // If the response is less than 4 bytes long, it is too short to tell, so return true, valid so far. if (response.StatusBuffer.Length < 4) { return(true); } string responseString = response.StatusBuffer.ToString(); // Otherwise, if there is no status code for this response yet, get one. if (response.Status == ResponseDescription.NoStatus) { // If the response does not start with three digits, then it is not a valid response from an FTP server. if (!(char.IsDigit(responseString[0]) && char.IsDigit(responseString[1]) && char.IsDigit(responseString[2]) && (responseString[3] == ' ' || responseString[3] == '-'))) { return(false); } else { response.StatusCodeString = responseString.Substring(0, 3); response.Status = Convert.ToInt16(response.StatusCodeString, NumberFormatInfo.InvariantInfo); } // IF a hyphen follows the status code on the first line of the response, then we have a multiline response coming. if (responseString[3] == '-') { response.Multiline = true; } } // If a complete line of response has been received from the server, then see if the // overall response is complete. // If this was not a multiline response, then the response is complete at the end of the line. // If this was a multiline response (indicated by three digits followed by a '-' in the first line), // then we see if the last line received started with the same three digits followed by a space. // If it did, then this is the sign of a complete multiline response. // If the line contained three other digits followed by the response, then this is a violation of the // FTP protocol for multiline responses. // All other cases indicate that the response is not yet complete. int index = 0; while ((index = responseString.IndexOf("\r\n", validThrough)) != -1) // gets the end line. { int lineStart = validThrough; validThrough = index + 2; // validThrough now marks the end of the line being examined. if (!response.Multiline) { completeLength = validThrough; return(true); } if (responseString.Length > lineStart + 4) { // If the first three characters of the response line currently being examined // match the status code, then if they are followed by a space, then we // have reached the end of the reply. if (responseString.Substring(lineStart, 3) == response.StatusCodeString) { if (responseString[lineStart + 3] == ' ') { completeLength = validThrough; return(true); } } } } return(true); }
// private bool PostReadCommandProcessing(ref Stream stream) { if (m_Index >= m_Commands.Length) { return(false); } // Set up front to prevent a race condition on result == PipelineInstruction.Pause m_DoSend = false; m_DoRead = false; PipelineInstruction result; PipelineEntry entry; if (m_Index == -1) { entry = null; } else { entry = m_Commands[m_Index]; } // Final QUIT command may get exceptions since the connectin // may be already closed by the server. So there is no response // to process, just advance the pipeline to continue if (m_CurrentResponseDescription == null && entry.Command == "QUIT\r\n") { result = PipelineInstruction.Advance; } else { result = PipelineCallback(entry, m_CurrentResponseDescription, false, ref stream); } if (result == PipelineInstruction.Abort) { Exception exception; if (m_AbortReason != string.Empty) { exception = new WebException(m_AbortReason); } else { exception = GenerateException(WebExceptionStatus.ServerProtocolViolation, null); } Abort(exception); throw exception; } else if (result == PipelineInstruction.Advance) { m_CurrentResponseDescription = null; m_DoSend = true; m_DoRead = true; m_Index++; } else if (result == PipelineInstruction.Pause) { // // PipelineCallback did an async operation and will have to re-enter again // Hold on for now // return(true); } else if (result == PipelineInstruction.GiveStream) { // // We will have another response coming, don't send // m_CurrentResponseDescription = null; m_DoRead = true; if (m_Async) { // If they block in the requestcallback we should still continue the pipeline ContinueCommandPipeline(); InvokeRequestCallback(stream); } return(true); } else if (result == PipelineInstruction.Reread) { // Another response is expected after this one m_CurrentResponseDescription = null; m_DoRead = true; } return(false); }
private PipelineInstruction QueueOrCreateDataConection(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream, out bool isSocketReady) { isSocketReady = false; if (_dataHandshakeStarted) { isSocketReady = true; return(PipelineInstruction.Pause); //if we already started then this is re-entering into the callback where we proceed with the stream } _dataHandshakeStarted = true; // Handle passive responses by parsing the port and later doing a Connect(...) bool isPassive = false; int port = -1; if (entry.Command == "PASV\r\n" || entry.Command == "EPSV\r\n") { if (!response.PositiveCompletion) { _abortReason = SR.Format(SR.net_ftp_server_failed_passive, response.Status); return(PipelineInstruction.Abort); } if (entry.Command == "PASV\r\n") { port = GetPortV4(response.StatusDescription); } else { port = GetPortV6(response.StatusDescription); } isPassive = true; } if (isPassive) { if (port == -1) { NetEventSource.Fail(this, "'port' not set."); } try { _dataSocket = CreateFtpDataSocket((FtpWebRequest)_request, Socket); } catch (ObjectDisposedException) { throw ExceptionHelper.RequestAbortedException; } IPEndPoint localEndPoint = new IPEndPoint(((IPEndPoint)Socket.LocalEndPoint).Address, 0); _dataSocket.Bind(localEndPoint); _passiveEndPoint = new IPEndPoint(ServerAddress, port); } PipelineInstruction result; if (_passiveEndPoint != null) { IPEndPoint passiveEndPoint = _passiveEndPoint; _passiveEndPoint = null; if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "starting Connect()"); } if (_isAsync) { _dataSocket.BeginConnect(passiveEndPoint, s_connectCallbackDelegate, this); result = PipelineInstruction.Pause; } else { _dataSocket.Connect(passiveEndPoint); result = PipelineInstruction.Advance; // for passive mode we end up going to the next command } } else { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "starting Accept()"); } if (_isAsync) { _dataSocket.BeginAccept(s_acceptCallbackDelegate, this); result = PipelineInstruction.Pause; } else { Socket listenSocket = _dataSocket; try { _dataSocket = _dataSocket.Accept(); if (!ServerAddress.Equals(((IPEndPoint)_dataSocket.RemoteEndPoint).Address)) { _dataSocket.Close(); throw new WebException(SR.net_ftp_active_address_different, WebExceptionStatus.ProtocolError); } isSocketReady = true; // for active mode we end up creating a stream before advancing the pipeline result = PipelineInstruction.Pause; } finally { listenSocket.Close(); } } } return(result); }
protected virtual PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream) { return(PipelineInstruction.Abort); }