/// <summary>
/// <para>Creates an array of commands, that will be sent to the server</para>
/// </summary>
protected override PipelineEntry [] BuildCommandsList(WebRequest req) {
bool resetLoggedInState = false;
FtpWebRequest request = (FtpWebRequest) req;
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "BuildCommandsList");
m_ResponseUri = request.RequestUri;
ArrayList commandList = new ArrayList();
#if DEBUG
// the Credentials.IsEqualTo method is only compiled in DEBUG so the assert must be restricted to DEBUG
// as well
// While some FTP servers support it, in general, the RFC's don't allow re-issuing the USER command to
// change the authentication context of an existing logged in connection. We prevent re-using existing
// connections if the credentials are different from the previous FtpWebRequest. Let's make sure that
// our connection pooling code is working correctly.
Debug.Assert(Credentials == null ||
Credentials.IsEqualTo(request.Credentials.GetCredential(request.RequestUri, "basic")),
"Should not be re-using an existing connection with different credentials");
#endif
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) {
m_LoginDirectory = null;
m_EstablishedServerDirectory = null;
m_RequestedServerDirectory = null;
m_CurrentTypeSetting = string.Empty;
if (m_LoginState == FtpLoginState.LoggedIn)
m_LoginState = FtpLoginState.LoggedInButNeedsRelogin;
}
if (m_LoginState != FtpLoginState.LoggedIn) {
Credentials = request.Credentials.GetCredential(request.RequestUri, "basic");
m_WelcomeMessage = new StringBuilder();
m_ExitMessage = new StringBuilder();
string domainUserName = string.Empty;
string password = string.Empty;
if (Credentials != null)
{
domainUserName = Credentials.InternalGetDomainUserName();
password = Credentials.InternalGetPassword();
}
if (domainUserName.Length == 0 && password.Length == 0)
{
domainUserName = "******";
password = "******";
}
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.GetString(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 (m_EstablishedServerDirectory != null && m_LoginDirectory != null && m_EstablishedServerDirectory != m_LoginDirectory)
{
commandList.Add(new PipelineEntry(FormatFtpCommand("CWD", m_LoginDirectory), PipelineEntryFlags.UserCommand));
m_RequestedServerDirectory = m_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));
m_RequestedServerDirectory = requestDirectory;
}
if (request.CacheProtocol != null && request.CacheProtocol.ProtocolStatus == CacheValidationStatus.DoNotTakeFromCache && request.MethodInfo.Operation == FtpOperation.DownloadFile)
commandList.Add(new PipelineEntry(FormatFtpCommand("MDTM", requestPath)));
if (!request.MethodInfo.IsCommandOnly)
{
// This is why having a protocol logic on the connection is a bad idea
if (request.CacheProtocol == null || request.CacheProtocol.ProtocolStatus != CacheValidationStatus.Continue)
{
string requestedTypeSetting = request.UseBinary ? "I" : "A";
if (m_CurrentTypeSetting != requestedTypeSetting) {
commandList.Add(new PipelineEntry(FormatFtpCommand("TYPE", requestedTypeSetting)));
m_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.CacheProtocol != null && request.CacheProtocol.ProtocolStatus == CacheValidationStatus.CombineCachedAndServerResponse)
{
// Combining partial cache with the reminder using "REST"
if (request.CacheProtocol.Validator.CacheEntry.StreamSize > 0)
commandList.Add(new PipelineEntry(FormatFtpCommand("REST", request.CacheProtocol.Validator.CacheEntry.StreamSize.ToString(CultureInfo.InvariantCulture))));
}
else 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))));
}
}
else
{
// revalidating GetFileSize = "SIZE" GetDateTimeStamp = "MDTM"
commandList.Add(new PipelineEntry(FormatFtpCommand("SIZE", requestPath)));
commandList.Add(new PipelineEntry(FormatFtpCommand("MDTM", requestPath)));
}
}
//
// Suppress the data file if this is a revalidation request
//
if (request.CacheProtocol == null || request.CacheProtocol.ProtocolStatus != CacheValidationStatus.Continue)
{
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));
}
if (!request.KeepAlive)
{
commandList.Add(new PipelineEntry(FormatFtpCommand("QUIT", null)));
}
}
return (PipelineEntry []) commandList.ToArray(typeof(PipelineEntry));
}