protected override PipelineEntry[] BuildCommandsList(WebRequest req)
{
bool resetLoggedInState = false;
FtpWebRequest request = (FtpWebRequest)req;
if (NetEventSource.IsEnabled) NetEventSource.Info(this);
_responseUri = request.RequestUri;
ArrayList commandList = new ArrayList();
if (request.EnableSsl && !UsingSecureStream)
{
commandList.Add(new PipelineEntry(FormatFtpCommand("AUTH", "TLS")));
// According to RFC we need to re-authorize with USER/PASS after we re-authenticate.
resetLoggedInState = true;
}
if (resetLoggedInState)
{
_loginDirectory = null;
_establishedServerDirectory = null;
_requestedServerDirectory = null;
_currentTypeSetting = string.Empty;
if (_loginState == FtpLoginState.LoggedIn)
_loginState = FtpLoginState.LoggedInButNeedsRelogin;
}
if (_loginState != FtpLoginState.LoggedIn)
{
Credentials = request.Credentials.GetCredential(request.RequestUri, "basic");
_welcomeMessage = new StringBuilder();
_exitMessage = new StringBuilder();
string domainUserName = string.Empty;
string password = string.Empty;
if (Credentials != null)
{
domainUserName = Credentials.UserName;
string domain = Credentials.Domain;
if (!string.IsNullOrEmpty(domain))
{
domainUserName = domain + "\\" + domainUserName;
}
password = Credentials.Password;
}
if (domainUserName.Length == 0 && password.Length == 0)
{
domainUserName = "anonymous";
password = "anonymous@";
}
commandList.Add(new PipelineEntry(FormatFtpCommand("USER", domainUserName)));
commandList.Add(new PipelineEntry(FormatFtpCommand("PASS", password), PipelineEntryFlags.DontLogParameter));
// If SSL, always configure data channel encryption after authentication to maximum RFC compatibility. The RFC allows for
// PBSZ/PROT commands to come either before or after the USER/PASS, but some servers require USER/PASS immediately after
// the AUTH TLS command.
if (request.EnableSsl && !UsingSecureStream)
{
commandList.Add(new PipelineEntry(FormatFtpCommand("PBSZ", "0")));
commandList.Add(new PipelineEntry(FormatFtpCommand("PROT", "P")));
}
commandList.Add(new PipelineEntry(FormatFtpCommand("OPTS", "utf8 on")));
commandList.Add(new PipelineEntry(FormatFtpCommand("PWD", null)));
}
GetPathOption getPathOption = GetPathOption.Normal;
if (request.MethodInfo.HasFlag(FtpMethodFlags.DoesNotTakeParameter))
{
getPathOption = GetPathOption.AssumeNoFilename;
}
else if (request.MethodInfo.HasFlag(FtpMethodFlags.ParameterIsDirectory))
{
getPathOption = GetPathOption.AssumeFilename;
}
string requestPath;
string requestDirectory;
string requestFilename;
GetPathInfo(getPathOption, request.RequestUri, out requestPath, out requestDirectory, out requestFilename);
if (requestFilename.Length == 0 && request.MethodInfo.HasFlag(FtpMethodFlags.TakesParameter))
throw new WebException(SR.net_ftp_invalid_uri);
// We optimize for having the current working directory staying at the login directory. This ensure that
// our relative paths work right and reduces unnecessary CWD commands.
// Usually, we don't change the working directory except for some FTP commands. If necessary,
// we need to reset our working directory back to the login directory.
if (_establishedServerDirectory != null && _loginDirectory != null && _establishedServerDirectory != _loginDirectory)
{
commandList.Add(new PipelineEntry(FormatFtpCommand("CWD", _loginDirectory), PipelineEntryFlags.UserCommand));
_requestedServerDirectory = _loginDirectory;
}
// For most commands, we don't need to navigate to the directory since we pass in the full
// path as part of the FTP protocol command. However, some commands require it.
if (request.MethodInfo.HasFlag(FtpMethodFlags.MustChangeWorkingDirectoryToPath) && requestDirectory.Length > 0)
{
commandList.Add(new PipelineEntry(FormatFtpCommand("CWD", requestDirectory), PipelineEntryFlags.UserCommand));
_requestedServerDirectory = requestDirectory;
}
if (!request.MethodInfo.IsCommandOnly)
{
string requestedTypeSetting = request.UseBinary ? "I" : "A";
if (_currentTypeSetting != requestedTypeSetting)
{
commandList.Add(new PipelineEntry(FormatFtpCommand("TYPE", requestedTypeSetting)));
_currentTypeSetting = requestedTypeSetting;
}
if (request.UsePassive)
{
string passiveCommand = (ServerAddress.AddressFamily == AddressFamily.InterNetwork) ? "PASV" : "EPSV";
commandList.Add(new PipelineEntry(FormatFtpCommand(passiveCommand, null), PipelineEntryFlags.CreateDataConnection));
}
else
{
string portCommand = (ServerAddress.AddressFamily == AddressFamily.InterNetwork) ? "PORT" : "EPRT";
CreateFtpListenerSocket(request);
commandList.Add(new PipelineEntry(FormatFtpCommand(portCommand, GetPortCommandLine(request))));
}
if (request.ContentOffset > 0)
{
// REST command must always be the last sent before the main file command is sent.
commandList.Add(new PipelineEntry(FormatFtpCommand("REST", request.ContentOffset.ToString(CultureInfo.InvariantCulture))));
}
}
PipelineEntryFlags flags = PipelineEntryFlags.UserCommand;
if (!request.MethodInfo.IsCommandOnly)
{
flags |= PipelineEntryFlags.GiveDataStream;
if (!request.UsePassive)
flags |= PipelineEntryFlags.CreateDataConnection;
}
if (request.MethodInfo.Operation == FtpOperation.Rename)
{
string baseDir = (requestDirectory == string.Empty)
? string.Empty : requestDirectory + "/";
commandList.Add(new PipelineEntry(FormatFtpCommand("RNFR", baseDir + requestFilename), flags));
string renameTo;
if (!string.IsNullOrEmpty(request.RenameTo)
&& request.RenameTo.StartsWith("/", StringComparison.OrdinalIgnoreCase))
{
renameTo = request.RenameTo; // Absolute path
}
else
{
renameTo = baseDir + request.RenameTo; // Relative path
}
commandList.Add(new PipelineEntry(FormatFtpCommand("RNTO", renameTo), flags));
}
else if (request.MethodInfo.HasFlag(FtpMethodFlags.DoesNotTakeParameter))
{
commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, string.Empty), flags));
}
else if (request.MethodInfo.HasFlag(FtpMethodFlags.MustChangeWorkingDirectoryToPath))
{
commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, requestFilename), flags));
}
else
{
commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, requestPath), flags));
}
commandList.Add(new PipelineEntry(FormatFtpCommand("QUIT", null)));
return (PipelineEntry[])commandList.ToArray(typeof(PipelineEntry));
}