// 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);
}