/// <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)); }
/// <summary> /// <para>Creates an array of commands, that will be sent to the server</para> /// </summary> protected override PipelineEntry [] BuildCommandsList(WebRequest req) { FtpWebRequest request = (FtpWebRequest) req; GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "BuildCommandsList"); m_ResponseUri = request.RequestUri; ArrayList commandList = new ArrayList(); if ((m_LastRequestWasUnknownMethod && !request.MethodInfo.IsUnknownMethod) || Credentials == null || !Credentials.IsEqualTo(request.Credentials.GetCredential(request.RequestUri, "basic"))) { m_PreviousServerPath = null; m_NewServerPath = null; m_LoginDirectory = null; if (m_LoginState == FtpLoginState.LoggedIn) m_LoginState = FtpLoginState.LoggedInButNeedsRelogin; } m_LastRequestWasUnknownMethod = request.MethodInfo.IsUnknownMethod; if (request.EnableSsl && !UsingSecureStream) { commandList.Add(new PipelineEntry(FormatFtpCommand("AUTH", "TLS"))); commandList.Add(new PipelineEntry(FormatFtpCommand("PBSZ", "0"))); commandList.Add(new PipelineEntry(FormatFtpCommand("PROT", "P"))); // According to RFC we need to re-authorize with USER/PASS after we re-authenticate. 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)); 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 = null; string requestFilename = null; GetPathAndFilename(getPathOption, request.RequestUri, ref requestPath, ref requestFilename, ref m_IsRootPath); if (requestFilename.Length == 0 && request.MethodInfo.HasFlag(FtpMethodFlags.TakesParameter)) throw new WebException(SR.GetString(SR.net_ftp_invalid_uri)); string newServerPath = requestPath; if (m_PreviousServerPath != newServerPath) { if (!m_IsRootPath && m_LoginState == FtpLoginState.LoggedIn && m_LoginDirectory != null) { newServerPath = m_LoginDirectory+newServerPath; } m_NewServerPath = newServerPath; commandList.Add(new PipelineEntry(FormatFtpCommand("CWD", newServerPath), PipelineEntryFlags.UserCommand)); } if (request.CacheProtocol != null && request.CacheProtocol.ProtocolStatus == CacheValidationStatus.DoNotTakeFromCache && request.MethodInfo.Operation == FtpOperation.DownloadFile) commandList.Add(new PipelineEntry(FormatFtpCommand("MDTM", requestFilename))); 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) { if (request.UseBinary) { commandList.Add(new PipelineEntry(FormatFtpCommand("TYPE", "I"))); } else { commandList.Add(new PipelineEntry(FormatFtpCommand("TYPE", "A"))); } 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", requestFilename))); commandList.Add(new PipelineEntry(FormatFtpCommand("MDTM", requestFilename))); } } // // 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) { commandList.Add(new PipelineEntry(FormatFtpCommand("RNFR", requestFilename), flags)); commandList.Add(new PipelineEntry(FormatFtpCommand("RNTO", request.RenameTo), flags)); } else { commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, requestFilename), flags)); } if (!request.KeepAlive) { commandList.Add(new PipelineEntry(FormatFtpCommand("QUIT", null))); } } return (PipelineEntry []) commandList.ToArray(typeof(PipelineEntry)); }