public void TestAdd_NonNullElement() { PipelineEntry e = new PipelineEntry(new DudPlugin()); Pipeline p = new Pipeline(); p.Add(e); Assert.AreEqual(1, p.Count); }
public void TestCreatePipeline_ValidInnerDefinition() { IPluginFactory f = new DudFactory(); PipelineDefinition d = new PipelineDefinition(); AlgorithmDefinition def = new AlgorithmDefinition("Test", new Property[] { }); d.Add(def); PluginPipelineFactory p = new PluginPipelineFactory(f); Pipeline pipe = p.CreatePipeline(d); Assert.AreEqual(1, pipe.Count); PipelineEntry e = pipe.First(); Assert.AreEqual(typeof(DudPlugin), e.Process.GetType()); }
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); }
// 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); }
protected virtual PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream) { return(PipelineInstruction.Abort); }
protected virtual PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream) { return PipelineInstruction.Abort; }
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; }
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; }
// 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; }
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 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; }