The Labs \ Source Viewer \ SSCLI \ System.Net \ FtpControlStream

  1. // ------------------------------------------------------------------------------
  2. // <copyright file="FtpControlStream.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. // ------------------------------------------------------------------------------
  15. //
  16. namespace System.Net
  17. {
  18.    
  19.     using System.Collections;
  20.     using System.IO;
  21.     using System.Security.Cryptography.X509Certificates;
  22.     using System.Net.Sockets;
  23.     using System.Security.Permissions;
  24.     using System.Text;
  25.     using System.Diagnostics;
  26.     using System.Globalization;
  27.     using System.Net.Cache;
  28.    
  29.     internal enum FtpPrimitive
  30.     {
  31.         Upload = 0,
  32.         Download = 1,
  33.         CommandOnly = 2
  34.     }
  35.    
  36.     internal enum FtpLoginState : byte
  37.     {
  38.         NotLoggedIn,
  39.         LoggedIn,
  40.         LoggedInButNeedsRelogin,
  41.         ReloginFailed
  42.     }
  43.    
  44.    
  45.     /// <devdoc>
  46.     /// <para>
  47.     /// The FtpControlStream class implements a basic FTP connection,
  48.     /// This means basic command sending and parsing.
  49.     /// Queuing is handled by the ConnectionPool, so that a Request is guarenteed
  50.     /// exclusive access to the Connection.
  51.     /// This is a pooled object, that will be stored in a pool when idle.
  52.     /// </para>
  53.     /// </devdoc>
  54.     internal class FtpControlStream : CommandStream
  55.     {
  56.        
  57.         private Socket m_DataSocket;
  58.         private IPEndPoint m_PassiveEndPoint;
  59.        
  60.         private StringBuilder m_BannerMessage;
  61.         private StringBuilder m_WelcomeMessage;
  62.         private StringBuilder m_ExitMessage;
  63.         private WeakReference m_Credentials;
  64.         private string m_Alias = null;
  65.         private bool m_IsRootPath;
  66.        
  67.         private long m_ContentLength = -1;
  68.         private DateTime m_LastModified;
  69.         private bool m_DataHandshakeStarted = false;
  70.         private string m_LoginDirectory;
  71.         private string m_PreviousServerPath;
  72.         private string m_NewServerPath;
  73.         private Uri m_ResponseUri;
  74.         private bool m_LastRequestWasUnknownMethod;
  75.        
  76.         private FtpLoginState m_LoginState = FtpLoginState.NotLoggedIn;
  77.        
  78.         internal FtpStatusCode StatusCode;
  79.         internal string StatusLine;
  80.        
  81.         internal NetworkCredential Credentials {
  82.             get {
  83.                 if (m_Credentials != null && m_Credentials.IsAlive) {
  84.                     return (NetworkCredential)m_Credentials.Target;
  85.                 }
  86.                 else {
  87.                     return null;
  88.                 }
  89.             }
  90.             set {
  91.                 if (m_Credentials == null) {
  92.                     m_Credentials = new WeakReference(null);
  93.                 }
  94.                 m_Credentials.Target = value;
  95.             }
  96.         }
  97.        
  98.         private static readonly AsyncCallback m_AcceptCallbackDelegate = new AsyncCallback(AcceptCallback);
  99.         private static readonly AsyncCallback m_ConnectCallbackDelegate = new AsyncCallback(ConnectCallback);
  100.         private static readonly AsyncCallback m_SSLHandshakeCallback = new AsyncCallback(SSLHandshakeCallback);
  101.        
  102.         /// <devdoc>
  103.         /// <para>
  104.         /// Setups and Creates a NetworkStream connection to the server
  105.         /// perform any initalization if needed
  106.         /// </para>
  107.         /// </devdoc>
  108.         internal FtpControlStream(ConnectionPool connectionPool, TimeSpan lifetime, bool checkLifetime) : base(connectionPool, lifetime, checkLifetime)
  109.         {
  110.         }
  111.        
  112.         /// <summary>
  113.         /// <para>Closes the connecting socket to generate an error.</para>
  114.         /// </summary>
  115.         internal void AbortConnect()
  116.         {
  117.             Socket socket = m_DataSocket;
  118.             if (socket != null) {
  119.                 try {
  120.                     socket.Close();
  121.                 }
  122.                 catch (ObjectDisposedException) {
  123.                 }
  124.             }
  125.         }
  126.        
  127.         /// <summary>
  128.         /// <para>Provides a wrapper for the async accept operations
  129.         /// </summary>
  130.         private static void AcceptCallback(IAsyncResult asyncResult)
  131.         {
  132.             FtpControlStream connection = (FtpControlStream)asyncResult.AsyncState;
  133.             LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult;
  134.             Socket listenSocket = (Socket)castedAsyncResult.AsyncObject;
  135.             try {
  136.                 connection.m_DataSocket = listenSocket.EndAccept(asyncResult);
  137.                 if (!connection.ServerAddress.Equals(((IPEndPoint)connection.m_DataSocket.RemoteEndPoint).Address)) {
  138.                     connection.m_DataSocket.Close();
  139.                     throw new WebException(SR.GetString(SR.net_ftp_active_address_different), WebExceptionStatus.ProtocolError);
  140.                 }
  141.                 connection.ContinueCommandPipeline();
  142.                
  143.             }
  144.             catch (Exception e) {
  145.                 connection.CloseSocket();
  146.                 connection.InvokeRequestCallback(e);
  147.             }
  148.             finally {
  149.                 listenSocket.Close();
  150.             }
  151.         }
  152.         /// <summary>
  153.         /// <para>Provides a wrapper for the async accept operations</para>
  154.         /// </summary>
  155.         private static void ConnectCallback(IAsyncResult asyncResult)
  156.         {
  157.             FtpControlStream connection = (FtpControlStream)asyncResult.AsyncState;
  158.             try {
  159.                 LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult;
  160.                 Socket dataSocket = (Socket)castedAsyncResult.AsyncObject;
  161.                 dataSocket.EndConnect(asyncResult);
  162.                 connection.ContinueCommandPipeline();
  163.             }
  164.             catch (Exception e) {
  165.                 connection.CloseSocket();
  166.                 connection.InvokeRequestCallback(e);
  167.             }
  168.         }
  169.        
  170.         //
  171.         // We issue a dummy read on the the SSL data stream to force SSL handshake
  172.         // This callback will will get stream to the user.
  173.         //
  174.         private static void SSLHandshakeCallback(IAsyncResult asyncResult)
  175.         {
  176.             FtpControlStream connection = (FtpControlStream)asyncResult.AsyncState;
  177.             try {
  178.                 connection.ContinueCommandPipeline();
  179.             }
  180.             catch (Exception e) {
  181.                 connection.CloseSocket();
  182.                 connection.InvokeRequestCallback(e);
  183.             }
  184.         }
  185.         // Creates a FtpDataStream object, constructs a TLS stream if needed.
  186.         // In case SSL we issue a 0 bytes read on that stream to force handshake.
  187.         // In case SSL and ASYNC we delay sigaling the user stream until the handshake is done.
  188.         //
  189.         private PipelineInstruction QueueOrCreateFtpDataStream(ref Stream stream)
  190.         {
  191.             if (m_DataSocket == null)
  192.                 throw new InternalException();
  193.            
  194.            
  195.             NetworkStream networkStream = new NetworkStream(m_DataSocket, true);
  196.            
  197.             stream = new FtpDataStream(networkStream, (FtpWebRequest)m_Request, IsFtpDataStreamWriteable());
  198.             return PipelineInstruction.GiveStream;
  199.         }
  200.        
  201.         /// <summary>
  202.         /// <para>Cleans up state variables for reuse of the connection</para>
  203.         /// </summary>
  204.         protected override void ClearState()
  205.         {
  206.             m_ContentLength = -1;
  207.             m_LastModified = DateTime.MinValue;
  208.             m_ResponseUri = null;
  209.             m_DataHandshakeStarted = false;
  210.             StatusCode = FtpStatusCode.Undefined;
  211.             StatusLine = null;
  212.            
  213.             m_DataSocket = null;
  214.             m_PassiveEndPoint = null;
  215.            
  216.             base.ClearState();
  217.         }
  218.         //
  219.         // This is called by underlying base class code, each time a new response is received from the wire or a protocol stage is resumed.
  220.         // This function controls the seting up of a data socket/connection, and of saving off the server responses
  221.         //
  222.         protected override PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream)
  223.         {
  224.             GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + ">" + (entry == null ? "null" : entry.Command));
  225.             GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + ">" + ((response == null) ? "null" : response.StatusDescription));
  226.            
  227.             // null response is not expected
  228.             if (response == null)
  229.                 return PipelineInstruction.Abort;
  230.            
  231.             FtpStatusCode status = (FtpStatusCode)response.Status;
  232.            
  233.             //
  234.             // Update global "current status" for FtpWebRequest
  235.             //
  236.             if (status != FtpStatusCode.ClosingControl) {
  237.                 // A 221 status won't be reflected on the user FTP response
  238.                 // Anything else will (by design?)
  239.                 StatusCode = status;
  240.                 StatusLine = response.StatusDescription;
  241.             }
  242.            
  243.             // If the status code is outside the range defined in RFC (1xx to 5xx) throw
  244.             if (response.InvalidStatusCode)
  245.                 throw new WebException(SR.GetString(SR.net_InvalidStatusCode), WebExceptionStatus.ProtocolError);
  246.            
  247.             if (m_Index == -1) {
  248.                 if (status == FtpStatusCode.SendUserCommand) {
  249.                     m_BannerMessage = new StringBuilder();
  250.                     m_BannerMessage.Append(StatusLine);
  251.                     return PipelineInstruction.Advance;
  252.                 }
  253.                 else if (status == FtpStatusCode.ServiceTemporarilyNotAvailable) {
  254.                     return PipelineInstruction.Reread;
  255.                 }
  256.                 else
  257.                     throw GenerateException(status, response.StatusDescription, null);
  258.             }
  259.            
  260.             //
  261.             // Check for the result of our attempt to use UTF8
  262.             // Condsider: optimize this for speed (avoid string compare) as that is the only command that may fail
  263.             //
  264.             if (entry.Command == "OPTS utf8 on\r\n") {
  265.                 if (response.PositiveCompletion) {
  266.                     Encoding = Encoding.UTF8;
  267.                 }
  268.                 else {
  269.                     Encoding = Encoding.Default;
  270.                 }
  271.                 return PipelineInstruction.Advance;
  272.             }
  273.            
  274.             // If we are already logged in and the server returns 530 then
  275.             // the server does not support re-issuing a USER command,
  276.             // tear down the connection and start all over again
  277.             if (entry.Command.IndexOf("USER") != -1) {
  278.                 // The server may not require a password for this user, so bypass the password command
  279.                 if (status == FtpStatusCode.LoggedInProceed) {
  280.                     m_LoginState = FtpLoginState.LoggedIn;
  281.                     m_Index++;
  282.                 }
  283.                 // The server does not like re-login
  284.                 // (We are logged in already but want to re-login under a different user)
  285.                 else if (status == FtpStatusCode.NotLoggedIn && m_LoginState != FtpLoginState.NotLoggedIn) {
  286.                     m_LoginState = FtpLoginState.ReloginFailed;
  287.                     throw ExceptionHelper.IsolatedException;
  288.                 }
  289.             }
  290.            
  291.             //
  292.             // Throw on an error with possibe recovery option
  293.             //
  294.             if (response.TransientFailure || response.PermanentFailure) {
  295.                 if (status == FtpStatusCode.ServiceNotAvailable) {
  296.                     MarkAsRecoverableFailure();
  297.                 }
  298.                 throw GenerateException(status, response.StatusDescription, null);
  299.             }
  300.            
  301.             if (m_LoginState != FtpLoginState.LoggedIn && entry.Command.IndexOf("PASS") != -1) {
  302.                 // Note the fact that we logged in
  303.                 if (status == FtpStatusCode.NeedLoginAccount || status == FtpStatusCode.LoggedInProceed)
  304.                     m_LoginState = FtpLoginState.LoggedIn;
  305.                 else
  306.                     throw GenerateException(status, response.StatusDescription, null);
  307.             }
  308.            
  309.             //
  310.             // Parse special cases
  311.             //
  312.             if (entry.HasFlag(PipelineEntryFlags.CreateDataConnection) && (response.PositiveCompletion || response.PositiveIntermediate)) {
  313.                 bool isSocketReady;
  314.                 PipelineInstruction result = QueueOrCreateDataConection(entry, response, timeout, ref stream, out isSocketReady);
  315.                 if (!isSocketReady)
  316.                     return result;
  317.                 // otheriwse we have a stream to create
  318.             }
  319.             //
  320.             // This is part of the above case and it's all about giving data stream back
  321.             //
  322.             if (status == FtpStatusCode.OpeningData || status == FtpStatusCode.DataAlreadyOpen) {
  323.                 if (m_DataSocket == null) {
  324.                     // a better diagnostic?
  325.                     return PipelineInstruction.Abort;
  326.                 }
  327.                 if (!entry.HasFlag(PipelineEntryFlags.GiveDataStream)) {
  328.                     m_AbortReason = SR.GetString(SR.net_ftp_invalid_status_response, status, entry.Command);
  329.                     return PipelineInstruction.Abort;
  330.                 }
  331.                
  332.                 // Parse out the Content length, if we can
  333.                 TryUpdateContentLength(response.StatusDescription);
  334.                
  335.                 // Parse out the file name, when it is returned and use it for our ResponseUri
  336.                 if (status == FtpStatusCode.OpeningData) {
  337.                     FtpWebRequest request = (FtpWebRequest)m_Request;
  338.                     if (request.MethodInfo.ShouldParseForResponseUri) {
  339.                         TryUpdateResponseUri(response.StatusDescription, request);
  340.                     }
  341.                 }
  342.                
  343.                 return QueueOrCreateFtpDataStream(ref stream);
  344.             }
  345.            
  346.            
  347.             //
  348.             // Parse responses by status code exclusivelly
  349.             //
  350.            
  351.             //Update our command list if we have an alias
  352.             if (status == FtpStatusCode.LoggedInProceed) {
  353.                 if (StatusLine.ToLower(CultureInfo.InvariantCulture).IndexOf("alias") > 0) {
  354.                     //find start of alias
  355.                     //skip first status code
  356.                     int i = StatusLine.IndexOf("230-", 3);
  357.                     if (i > 0) {
  358.                         i += 4;
  359.                         //eat white space
  360.                         while (i < StatusLine.Length && StatusLine[i] == ' ') {
  361.                             i++;
  362.                         }
  363.                        
  364.                         //not eol
  365.                         if (i < StatusLine.Length) {
  366.                             //get end of alias
  367.                             int j = StatusLine.IndexOf(' ', i);
  368.                             if (j < 0)
  369.                                 j = StatusLine.Length;
  370.                            
  371.                             m_Alias = StatusLine.Substring(i, j - i);
  372.                            
  373.                             if (!m_IsRootPath) {
  374.                                 //update command list
  375.                                 for (i = 0; i < m_Commands.Length; i++) {
  376.                                     if (m_Commands[i].Command.IndexOf("CWD") == 0) {
  377.                                         string path = m_Alias + m_NewServerPath;
  378.                                         m_Commands[i] = new PipelineEntry(FormatFtpCommand("CWD", path));
  379.                                         break;
  380.                                     }
  381.                                 }
  382.                             }
  383.                         }
  384.                     }
  385.                 }
  386.                 m_WelcomeMessage.Append(StatusLine);
  387.             }
  388.             // OR set the user response ExitMessage
  389.             else if (status == FtpStatusCode.ClosingControl) {
  390.                 m_ExitMessage.Append(response.StatusDescription);
  391.                 // And close the control stream socket on "QUIT"
  392.                 CloseSocket();
  393.             }
  394.             // OR parse out the file size or file time, usually a result of sending SIZE/MDTM commands
  395.             else if (status == FtpStatusCode.FileStatus) {
  396.                 FtpWebRequest request = (FtpWebRequest)m_Request;
  397.                 if (entry.Command.StartsWith("SIZE ")) {
  398.                     m_ContentLength = GetContentLengthFrom213Response(response.StatusDescription);
  399.                 }
  400.                 else if (entry.Command.StartsWith("MDTM ")) {
  401.                     m_LastModified = GetLastModifiedFrom213Response(response.StatusDescription);
  402.                 }
  403.             }
  404.             // OR parse out our login directory
  405.             else if (status == FtpStatusCode.PathnameCreated) {
  406.                 if (entry.Command == "PWD\r\n" && !entry.HasFlag(PipelineEntryFlags.UserCommand)) {
  407.                     m_LoginDirectory = GetLoginDirectory(response.StatusDescription);
  408.                     if (!m_IsRootPath && m_LoginDirectory != "\\" && m_LoginDirectory != "/" && m_Alias == null) {
  409.                         //update command list
  410.                         for (int i = 0; i < m_Commands.Length; i++) {
  411.                             if (m_Commands[i].Command.IndexOf("CWD") == 0) {
  412.                                 string path = m_LoginDirectory + m_NewServerPath;
  413.                                 m_Commands[i] = new PipelineEntry(FormatFtpCommand("CWD", path));
  414.                                 break;
  415.                             }
  416.                         }
  417.                     }
  418.                 }
  419.             }
  420.             // Asserting we have some positive response
  421.             else {
  422.                 // OR update the current path (optimize this pls)
  423.                 if (entry.Command.IndexOf("CWD") != -1) {
  424.                     m_PreviousServerPath = m_NewServerPath;
  425.                 }
  426.             }
  427.            
  428.             // Intermidate responses require rereading
  429.             if (response.PositiveIntermediate || (!UsingSecureStream && entry.Command == "AUTH TLS\r\n")) {
  430.                 return PipelineInstruction.Reread;
  431.             }
  432.            
  433.             return PipelineInstruction.Advance;
  434.         }
  435.        
  436.         /// <summary>
  437.         /// <para>Creates an array of commands, that will be sent to the server</para>
  438.         /// </summary>
  439.         protected override PipelineEntry[] BuildCommandsList(WebRequest req)
  440.         {
  441.             FtpWebRequest request = (FtpWebRequest)req;
  442.             GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "BuildCommandsList");
  443.             m_ResponseUri = request.RequestUri;
  444.             ArrayList commandList = new ArrayList();
  445.             if ((m_LastRequestWasUnknownMethod && !request.MethodInfo.IsUnknownMethod) || Credentials == null || !Credentials.IsEqualTo(request.Credentials.GetCredential(request.RequestUri, "basic"))) {
  446.                 m_PreviousServerPath = null;
  447.                 m_NewServerPath = null;
  448.                 m_LoginDirectory = null;
  449.                 if (m_LoginState == FtpLoginState.LoggedIn)
  450.                     m_LoginState = FtpLoginState.LoggedInButNeedsRelogin;
  451.             }
  452.            
  453.             m_LastRequestWasUnknownMethod = request.MethodInfo.IsUnknownMethod;
  454.            
  455.             if (request.EnableSsl && !UsingSecureStream) {
  456.                 commandList.Add(new PipelineEntry(FormatFtpCommand("AUTH", "TLS")));
  457.                 commandList.Add(new PipelineEntry(FormatFtpCommand("PBSZ", "0")));
  458.                 commandList.Add(new PipelineEntry(FormatFtpCommand("PROT", "P")));
  459.                 // According to RFC we need to re-authorize with USER/PASS after we re-authenticate.
  460.                 if (m_LoginState == FtpLoginState.LoggedIn)
  461.                     m_LoginState = FtpLoginState.LoggedInButNeedsRelogin;
  462.             }
  463.            
  464.             if (m_LoginState != FtpLoginState.LoggedIn) {
  465.                 Credentials = request.Credentials.GetCredential(request.RequestUri, "basic");
  466.                 m_WelcomeMessage = new StringBuilder();
  467.                 m_ExitMessage = new StringBuilder();
  468.                
  469.                 string domainUserName = string.Empty;
  470.                 string password = string.Empty;
  471.                
  472.                 if (Credentials != null) {
  473.                     domainUserName = Credentials.InternalGetDomainUserName();
  474.                     password = Credentials.InternalGetPassword();
  475.                 }
  476.                
  477.                 if (domainUserName.Length == 0 && password.Length == 0) {
  478.                     domainUserName = "anonymous";
  479.                     password = "anonymous@";
  480.                 }
  481.                
  482.                 commandList.Add(new PipelineEntry(FormatFtpCommand("USER", domainUserName)));
  483.                 commandList.Add(new PipelineEntry(FormatFtpCommand("PASS", password), PipelineEntryFlags.DontLogParameter));
  484.                 commandList.Add(new PipelineEntry(FormatFtpCommand("OPTS", "utf8 on")));
  485.                 commandList.Add(new PipelineEntry(FormatFtpCommand("PWD", null)));
  486.             }
  487.            
  488.             GetPathOption getPathOption = GetPathOption.Normal;
  489.            
  490.             if (request.MethodInfo.HasFlag(FtpMethodFlags.DoesNotTakeParameter)) {
  491.                 getPathOption = GetPathOption.AssumeNoFilename;
  492.             }
  493.             else if (request.MethodInfo.HasFlag(FtpMethodFlags.ParameterIsDirectory)) {
  494.                 getPathOption = GetPathOption.AssumeFilename;
  495.             }
  496.            
  497.             string requestPath = null;
  498.             string requestFilename = null;
  499.             GetPathAndFilename(getPathOption, request.RequestUri, ref requestPath, ref requestFilename, ref m_IsRootPath);
  500.            
  501.             if (requestFilename.Length == 0 && request.MethodInfo.HasFlag(FtpMethodFlags.TakesParameter))
  502.                 throw new WebException(SR.GetString(SR.net_ftp_invalid_uri));
  503.            
  504.             string newServerPath = requestPath;
  505.             if (m_PreviousServerPath != newServerPath) {
  506.                 if (!m_IsRootPath && m_LoginState == FtpLoginState.LoggedIn && m_LoginDirectory != null) {
  507.                     newServerPath = m_LoginDirectory + newServerPath;
  508.                 }
  509.                 m_NewServerPath = newServerPath;
  510.                
  511.                 commandList.Add(new PipelineEntry(FormatFtpCommand("CWD", newServerPath), PipelineEntryFlags.UserCommand));
  512.             }
  513.            
  514.             if (request.CacheProtocol != null && request.CacheProtocol.ProtocolStatus == CacheValidationStatus.DoNotTakeFromCache && request.MethodInfo.Operation == FtpOperation.DownloadFile)
  515.                 commandList.Add(new PipelineEntry(FormatFtpCommand("MDTM", requestFilename)));
  516.            
  517.             if (!request.MethodInfo.IsCommandOnly) {
  518.                 // This is why having a protocol logic on the connection is a bad idea
  519.                 if (request.CacheProtocol == null || request.CacheProtocol.ProtocolStatus != CacheValidationStatus.Continue) {
  520.                     if (request.UseBinary) {
  521.                         commandList.Add(new PipelineEntry(FormatFtpCommand("TYPE", "I")));
  522.                     }
  523.                     else {
  524.                         commandList.Add(new PipelineEntry(FormatFtpCommand("TYPE", "A")));
  525.                     }
  526.                    
  527.                     if (request.UsePassive) {
  528.                         string passiveCommand = (ServerAddress.AddressFamily == AddressFamily.InterNetwork) ? "PASV" : "EPSV";
  529.                         commandList.Add(new PipelineEntry(FormatFtpCommand(passiveCommand, null), PipelineEntryFlags.CreateDataConnection));
  530.                     }
  531.                     else {
  532.                         string portCommand = (ServerAddress.AddressFamily == AddressFamily.InterNetwork) ? "PORT" : "EPRT";
  533.                         CreateFtpListenerSocket(request);
  534.                         commandList.Add(new PipelineEntry(FormatFtpCommand(portCommand, GetPortCommandLine(request))));
  535.                     }
  536.                    
  537.                     if (request.CacheProtocol != null && request.CacheProtocol.ProtocolStatus == CacheValidationStatus.CombineCachedAndServerResponse) {
  538.                         // Combining partial cache with the reminder using "REST"
  539.                         if (request.CacheProtocol.Validator.CacheEntry.StreamSize > 0)
  540.                             commandList.Add(new PipelineEntry(FormatFtpCommand("REST", request.CacheProtocol.Validator.CacheEntry.StreamSize.ToString(CultureInfo.InvariantCulture))));
  541.                     }
  542.                     else if (request.ContentOffset > 0) {
  543.                         // REST command must always be the last sent before the main file command is sent.
  544.                         commandList.Add(new PipelineEntry(FormatFtpCommand("REST", request.ContentOffset.ToString(CultureInfo.InvariantCulture))));
  545.                     }
  546.                 }
  547.                 else {
  548.                     // revalidating GetFileSize = "SIZE" GetDateTimeStamp = "MDTM"
  549.                     commandList.Add(new PipelineEntry(FormatFtpCommand("SIZE", requestFilename)));
  550.                     commandList.Add(new PipelineEntry(FormatFtpCommand("MDTM", requestFilename)));
  551.                 }
  552.             }
  553.            
  554.             //
  555.             // Suppress the data file if this is a revalidation request
  556.             //
  557.             if (request.CacheProtocol == null || request.CacheProtocol.ProtocolStatus != CacheValidationStatus.Continue) {
  558.                 PipelineEntryFlags flags = PipelineEntryFlags.UserCommand;
  559.                 if (!request.MethodInfo.IsCommandOnly) {
  560.                     flags |= PipelineEntryFlags.GiveDataStream;
  561.                     if (!request.UsePassive)
  562.                         flags |= PipelineEntryFlags.CreateDataConnection;
  563.                 }
  564.                
  565.                 if (request.MethodInfo.Operation == FtpOperation.Rename) {
  566.                     commandList.Add(new PipelineEntry(FormatFtpCommand("RNFR", requestFilename), flags));
  567.                     commandList.Add(new PipelineEntry(FormatFtpCommand("RNTO", request.RenameTo), flags));
  568.                 }
  569.                 else {
  570.                     commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, requestFilename), flags));
  571.                 }
  572.                 if (!request.KeepAlive) {
  573.                     commandList.Add(new PipelineEntry(FormatFtpCommand("QUIT", null)));
  574.                 }
  575.             }
  576.            
  577.             return (PipelineEntry[])commandList.ToArray(typeof(PipelineEntry));
  578.         }
  579.        
  580.         private PipelineInstruction QueueOrCreateDataConection(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream, out bool isSocketReady)
  581.         {
  582.             isSocketReady = false;
  583.             if (m_DataHandshakeStarted) {
  584.                 isSocketReady = true;
  585.                 return PipelineInstruction.Pause;
  586.                 //if we already started then this is re-entering into the callback where we proceed with the stream
  587.             }
  588.            
  589.             m_DataHandshakeStarted = true;
  590.            
  591.             // handle passive responses by parsing the port and later doing a Connect(...)
  592.             bool isPassive = false;
  593.             int port = -1;
  594.             if (entry.Command == "PASV\r\n" || entry.Command == "EPSV\r\n") {
  595.                 if (!response.PositiveCompletion) {
  596.                     m_AbortReason = SR.GetString(SR.net_ftp_server_failed_passive, response.Status);
  597.                     return PipelineInstruction.Abort;
  598.                 }
  599.                 if (entry.Command == "PASV\r\n") {
  600.                     IPAddress serverReturnedAddress = null;
  601.                     port = GetAddressAndPort(response.StatusDescription, ref serverReturnedAddress);
  602.                     if (!ServerAddress.Equals(serverReturnedAddress))
  603.                         throw new WebException(SR.GetString(SR.net_ftp_passive_address_different));
  604.                 }
  605.                 else {
  606.                     port = GetPortV6(response.StatusDescription);
  607.                 }
  608.                
  609.                 isPassive = true;
  610.             }
  611.            
  612.             new SocketPermission(PermissionState.Unrestricted).Assert();
  613.            
  614.             try {
  615.                 if (isPassive) {
  616.                     GlobalLog.Assert(port != -1, "FtpControlStream#{0}|'port' not set.", ValidationHelper.HashString(this));
  617.                    
  618.                     try {
  619.                         m_DataSocket = CreateFtpDataSocket((FtpWebRequest)m_Request, Socket);
  620.                     }
  621.                     catch (ObjectDisposedException) {
  622.                         throw ExceptionHelper.RequestAbortedException;
  623.                     }
  624.                     m_PassiveEndPoint = new IPEndPoint(ServerAddress, port);
  625.                 }
  626.                
  627.                 PipelineInstruction result;
  628.                
  629.                 if (m_PassiveEndPoint != null) {
  630.                     IPEndPoint passiveEndPoint = m_PassiveEndPoint;
  631.                     m_PassiveEndPoint = null;
  632.                     GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "starting Connect()");
  633.                     if (m_Async) {
  634.                         m_DataSocket.BeginConnect(passiveEndPoint, m_ConnectCallbackDelegate, this);
  635.                         result = PipelineInstruction.Pause;
  636.                     }
  637.                     else {
  638.                         m_DataSocket.Connect(passiveEndPoint);
  639.                         result = PipelineInstruction.Advance;
  640.                         // for passive mode we end up going to the next command
  641.                     }
  642.                 }
  643.                 else {
  644.                     GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "starting Accept()");
  645.                     if (m_Async) {
  646.                         m_DataSocket.BeginAccept(m_AcceptCallbackDelegate, this);
  647.                         result = PipelineInstruction.Pause;
  648.                     }
  649.                     else {
  650.                         Socket listenSocket = m_DataSocket;
  651.                         try {
  652.                             m_DataSocket = m_DataSocket.Accept();
  653.                             if (!ServerAddress.Equals(((IPEndPoint)m_DataSocket.RemoteEndPoint).Address)) {
  654.                                 m_DataSocket.Close();
  655.                                 throw new WebException(SR.GetString(SR.net_ftp_active_address_different), WebExceptionStatus.ProtocolError);
  656.                             }
  657.                             isSocketReady = true;
  658.                             // for active mode we end up creating a stream before advancing the pipeline
  659.                             result = PipelineInstruction.Pause;
  660.                         }
  661.                         finally {
  662.                             listenSocket.Close();
  663.                         }
  664.                     }
  665.                 }
  666.                 return result;
  667.             }
  668.             finally {
  669.                 SocketPermission.RevertAssert();
  670.             }
  671.         }
  672.        
  673.         //
  674.         // A door into protected CloseSocket() method
  675.         //
  676.         internal void Quit()
  677.         {
  678.             CloseSocket();
  679.         }
  680.        
  681.         private enum GetPathOption
  682.         {
  683.             Normal,
  684.             AssumeFilename,
  685.             AssumeNoFilename
  686.         }
  687.        
  688.         /// <summary>
  689.         /// <para>Gets the path componet of the Uri</para>
  690.         /// </summary>
  691.         private static void GetPathAndFilename(GetPathOption pathOption, Uri uri, ref string path, ref string filename, ref bool isRoot)
  692.         {
  693.             string tempPath = uri.GetParts(UriComponents.Path | UriComponents.KeepDelimiter, UriFormat.Unescaped);
  694.             isRoot = false;
  695.             if (tempPath.StartsWith("//")) {
  696.                 isRoot = true;
  697.                 tempPath = tempPath.Substring(1, tempPath.Length - 1);
  698.             }
  699.             int index = tempPath.LastIndexOf('/');
  700.             switch (pathOption) {
  701.                 case GetPathOption.AssumeFilename:
  702.                     if (index != -1 && index == tempPath.Length - 1) {
  703.                         // Remove last '/' and continue normal processing
  704.                         tempPath = tempPath.Substring(0, tempPath.Length - 1);
  705.                         index = tempPath.LastIndexOf('/');
  706.                     }
  707.                     path = tempPath.Substring(0, index + 1);
  708.                     filename = tempPath.Substring(index + 1, tempPath.Length - (index + 1));
  709.                     break;
  710.                 case GetPathOption.AssumeNoFilename:
  711.                     path = tempPath;
  712.                     filename = "";
  713.                     break;
  714.                 case GetPathOption.Normal:
  715.                 default:
  716.                     path = tempPath.Substring(0, index + 1);
  717.                     filename = tempPath.Substring(index + 1, tempPath.Length - (index + 1));
  718.                     break;
  719.             }
  720.            
  721.             if (path.Length == 0)
  722.                 path = "/";
  723.         }
  724.        
  725.         //
  726.         /// <summary>
  727.         /// <para>Formats an IP address (contained in a UInt32) to a FTP style command string</para>
  728.         /// </summary>
  729.         private string FormatAddress(IPAddress address, int Port)
  730.         {
  731.             byte[] localAddressInBytes = address.GetAddressBytes();
  732.            
  733.             // produces a string in FTP IPAddress/Port encoding (a1, a2, a3, a4, p1, p2), for sending as a parameter
  734.             // to the port command.
  735.             StringBuilder sb = new StringBuilder(32);
  736.             foreach (byte element in localAddressInBytes) {
  737.                 sb.Append(element);
  738.                 sb.Append(',');
  739.             }
  740.             sb.Append(Port / 256);
  741.             sb.Append(',');
  742.             sb.Append(Port % 256);
  743.             return sb.ToString();
  744.         }
  745.        
  746.         /// <summary>
  747.         /// <para>Formats an IP address (v6) to a FTP style command string
  748.         /// Looks something in this form: |2|1080::8:800:200C:417A|5282| <para>
  749.         /// |2|4567::0123:5678:0123:5678|0123|
  750.         /// </summary>
  751.         private string FormatAddressV6(IPAddress address, int port)
  752.         {
  753.             StringBuilder sb = new StringBuilder(43);
  754.             // based on max size of IPv6 address + port + seperators
  755.             string addressString = address.ToString();
  756.             sb.Append("|2|");
  757.             sb.Append(addressString);
  758.             sb.Append('|');
  759.             sb.Append(port.ToString(NumberFormatInfo.InvariantInfo));
  760.             sb.Append('|');
  761.             return sb.ToString();
  762.         }
  763.        
  764.         internal long ContentLength {
  765.             get { return m_ContentLength; }
  766.         }
  767.        
  768.         internal DateTime LastModified {
  769.             get { return m_LastModified; }
  770.         }
  771.        
  772.         internal Uri ResponseUri {
  773.             get { return m_ResponseUri; }
  774.         }
  775.        
  776.         /// <summary>
  777.         /// <para>Returns the server message sent before user credentials are sent</para>
  778.         /// </summary>
  779.         internal string BannerMessage {
  780.             get { return (m_BannerMessage != null) ? m_BannerMessage.ToString() : null; }
  781.         }
  782.        
  783.         /// <summary>
  784.         /// <para>Returns the server message sent after user credentials are sent</para>
  785.         /// </summary>
  786.         internal string WelcomeMessage {
  787.             get { return (m_WelcomeMessage != null) ? m_WelcomeMessage.ToString() : null; }
  788.         }
  789.        
  790.         /// <summary>
  791.         /// <para>Returns the exit sent message on shutdown</para>
  792.         /// </summary>
  793.         internal string ExitMessage {
  794.             get { return (m_ExitMessage != null) ? m_ExitMessage.ToString() : null; }
  795.         }
  796.        
  797.         /// <summary>
  798.         /// <para>Parses a response string for content length</para>
  799.         /// </summary>
  800.         private long GetContentLengthFrom213Response(string responseString)
  801.         {
  802.             string[] parsedList = responseString.Split(new char[] {' '});
  803.             if (parsedList.Length < 2)
  804.                 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
  805.             return Convert.ToInt64(parsedList[1], NumberFormatInfo.InvariantInfo);
  806.         }
  807.        
  808.         /// <summary>
  809.         /// <para>Parses a response string for last modified time</para>
  810.         /// </summary>
  811.         private DateTime GetLastModifiedFrom213Response(string str)
  812.         {
  813.             DateTime dateTime = m_LastModified;
  814.             string[] parsedList = str.Split(new char[] {' ', '.'});
  815.             if (parsedList.Length < 2) {
  816.                 return dateTime;
  817.             }
  818.             string dateTimeLine = parsedList[1];
  819.             if (dateTimeLine.Length < 14) {
  820.                 return dateTime;
  821.             }
  822.             int year = Convert.ToInt32(dateTimeLine.Substring(0, 4), NumberFormatInfo.InvariantInfo);
  823.             int month = Convert.ToInt16(dateTimeLine.Substring(4, 2), NumberFormatInfo.InvariantInfo);
  824.             int day = Convert.ToInt16(dateTimeLine.Substring(6, 2), NumberFormatInfo.InvariantInfo);
  825.             int hour = Convert.ToInt16(dateTimeLine.Substring(8, 2), NumberFormatInfo.InvariantInfo);
  826.             int minute = Convert.ToInt16(dateTimeLine.Substring(10, 2), NumberFormatInfo.InvariantInfo);
  827.             int second = Convert.ToInt16(dateTimeLine.Substring(12, 2), NumberFormatInfo.InvariantInfo);
  828.             int millisecond = 0;
  829.             if (parsedList.Length > 2) {
  830.                 millisecond = Convert.ToInt16(parsedList[2], NumberFormatInfo.InvariantInfo);
  831.             }
  832.             try {
  833.                 dateTime = new DateTime(year, month, day, hour, minute, second, millisecond);
  834.                 dateTime = dateTime.ToLocalTime();
  835.                 // must be handled in local time
  836.             }
  837.             catch (ArgumentOutOfRangeException) {
  838.             }
  839.             catch (ArgumentException) {
  840.             }
  841.             return dateTime;
  842.         }
  843.        
  844.         /// <summary>
  845.         /// <para>Attempts to find the response Uri
  846.         /// Typical string looks like this, need to get trailing filename
  847.         /// "150 Opening BINARY mode data connection for FTP46.tmp."</para>
  848.         /// </summary>
  849.         private void TryUpdateResponseUri(string str, FtpWebRequest request)
  850.         {
  851.             Uri baseUri = request.RequestUri;
  852.             //
  853.             // Not sure what we are doing here but I guess the logic is IIS centric
  854.             //
  855.             int start = str.IndexOf("for ");
  856.             if (start == -1)
  857.                 return;
  858.             start += 4;
  859.             int end = str.LastIndexOf('(');
  860.             if (end == -1)
  861.                 end = str.Length;
  862.             if (end <= start)
  863.                 return;
  864.            
  865.             string filename = str.Substring(start, end - start);
  866.             filename = filename.TrimEnd(new char[] {' ', '.', '\r', '\n'});
  867.             // Do minimal escaping that we need to get a valid Uri
  868.             // when combined with the baseUri
  869.             string escapedFilename;
  870.             escapedFilename = filename.Replace("%", "%25");
  871.             escapedFilename = escapedFilename.Replace("#", "%23");
  872.            
  873.             // help us out if the user forgot to add a slash to the directory name
  874.             string orginalPath = baseUri.AbsolutePath;
  875.             if (orginalPath.Length > 0 && orginalPath[orginalPath.Length - 1] != '/') {
  876.                 UriBuilder uriBuilder = new UriBuilder(baseUri);
  877.                 uriBuilder.Path = orginalPath + "/";
  878.                 baseUri = uriBuilder.Uri;
  879.             }
  880.            
  881.             Uri newUri;
  882.             if (!Uri.TryCreate(baseUri, escapedFilename, out newUri)) {
  883.                 throw new FormatException(SR.GetString(SR.net_ftp_invalid_response_filename, filename));
  884.             }
  885.             else {
  886.                 if (!baseUri.IsBaseOf(newUri) || baseUri.Segments.Length != newUri.Segments.Length - 1) {
  887.                     throw new FormatException(SR.GetString(SR.net_ftp_invalid_response_filename, filename));
  888.                 }
  889.                 else {
  890.                     m_ResponseUri = newUri;
  891.                 }
  892.             }
  893.         }
  894.        
  895.         /// <summary>
  896.         /// <para>Parses a response string for content length</para>
  897.         /// </summary>
  898.         private void TryUpdateContentLength(string str)
  899.         {
  900.             int pos1 = str.LastIndexOf("(");
  901.             if (pos1 != -1) {
  902.                 int pos2 = str.IndexOf(" bytes).");
  903.                 if (pos2 != -1 && pos2 > pos1) {
  904.                     pos1++;
  905.                     long result;
  906.                     if (Int64.TryParse(str.Substring(pos1, pos2 - pos1), NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo, out result)) {
  907.                         m_ContentLength = result;
  908.                     }
  909.                 }
  910.             }
  911.         }
  912.        
  913.         /// <summary>
  914.         /// <para>Parses a response string for a an IP Address</para>
  915.         /// </summary>
  916. /*
  917.         private string GetIPAddress(string str)
  918.         {
  919.             StringBuilder IPstr=new StringBuilder(32);
  920.             string Substr = null;
  921.             int pos1 = str.IndexOf("(")+1;
  922.             int pos2 = str.IndexOf(",");
  923.             for(int i =0; i<3;i++)
  924.             {
  925.                 Substr = str.Substring(pos1,pos2-pos1)+".";
  926.                 IPstr.Append(Substr);
  927.                 pos1 = pos2+1;
  928.                 pos2 = str.IndexOf(",",pos1);
  929.             }
  930.             Substr = str.Substring(pos1,pos2-pos1);
  931.             IPstr.Append(Substr);
  932.             return IPstr.ToString();
  933.         }
  934.         */       
  935.        
  936.         /// <summary>
  937.         /// <para>Parses a response string for our login dir in " "</para>
  938.         /// </summary>
  939.         private string GetLoginDirectory(string str)
  940.         {
  941.             int firstQuote = str.IndexOf('"');
  942.             int lastQuote = str.LastIndexOf('"');
  943.             if (firstQuote != -1 && lastQuote != -1 && firstQuote != lastQuote) {
  944.                 return str.Substring(firstQuote + 1, lastQuote - firstQuote - 1);
  945.             }
  946.             else {
  947.                 return String.Empty;
  948.             }
  949.         }
  950.        
  951.         /// <summary>
  952.         /// <para>Parses a response string for a port number</para>
  953.         /// </summary>
  954.         private int GetAddressAndPort(string responseString, ref IPAddress ipAddress)
  955.         {
  956.             int port = 0;
  957.             const int firstPortDigit = 5;
  958.             const int secondPortDigit = 6;
  959.            
  960.             string[] parsedList = responseString.Split(new char[] {'(', ',', ')'});
  961.             if (secondPortDigit >= parsedList.Length) {
  962.                 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
  963.                
  964.             }
  965.             port = Convert.ToInt32(parsedList[firstPortDigit], NumberFormatInfo.InvariantInfo) * 256;
  966.             port = port + Convert.ToInt32(parsedList[secondPortDigit], NumberFormatInfo.InvariantInfo);
  967.             Int64 address = 0;
  968.             try {
  969.                 for (int i = 4; i > 0; i--) {
  970.                     address = (address << 8) + Convert.ToByte(parsedList[i], NumberFormatInfo.InvariantInfo);
  971.                 }
  972.             }
  973.             catch {
  974.                 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
  975.             }
  976.             ipAddress = new IPAddress(address);
  977.             return port;
  978.         }
  979.        
  980.         /// <summary>
  981.         /// <para>Parses a response string for a port number</para>
  982.         /// </summary>
  983.         private int GetPortV6(string responseString)
  984.         {
  985.             int pos1 = responseString.LastIndexOf("(");
  986.             int pos2 = responseString.LastIndexOf(")");
  987.             if (pos1 == -1 || pos2 <= pos1)
  988.                 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
  989.            
  990.             // addressInfo will contain a string of format "|||<tcp-port>|"
  991.             string addressInfo = responseString.Substring(pos1 + 1, pos2 - pos1 - 1);
  992.            
  993.            
  994.             string[] parsedList = addressInfo.Split(new char[] {'|'});
  995.             if (parsedList.Length < 4)
  996.                 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
  997.            
  998.             return Convert.ToInt32(parsedList[3], NumberFormatInfo.InvariantInfo);
  999.         }
  1000.        
  1001.         /// <summary>
  1002.         /// <para>Creates the Listener socket</para>
  1003.         /// </summary>
  1004.         private void CreateFtpListenerSocket(FtpWebRequest request)
  1005.         {
  1006.             // see \\index1\sdnt\inetcore\wininet\ftp
  1007.             // gets an IPEndPoint for the local host for the data socket to bind to.
  1008.             IPEndPoint epListener = new IPEndPoint(((IPEndPoint)Socket.LocalEndPoint).Address, 0);
  1009.             try {
  1010.                 m_DataSocket = CreateFtpDataSocket(request, Socket);
  1011.             }
  1012.             catch (ObjectDisposedException) {
  1013.                 throw ExceptionHelper.RequestAbortedException;
  1014.             }
  1015.            
  1016.             new SocketPermission(PermissionState.Unrestricted).Assert();
  1017.            
  1018.             try {
  1019.                 // binds the data socket to the local end point.
  1020.                 m_DataSocket.Bind(epListener);
  1021.                 m_DataSocket.Listen(1);
  1022.                 // Put the dataSocket * & in Listen mode
  1023.             }
  1024.             finally {
  1025.                 SocketPermission.RevertAssert();
  1026.             }
  1027.         }
  1028.        
  1029.        
  1030.         /// <summary>
  1031.         /// <para>Builds a command line to send to the server with proper port and IP address of client</para>
  1032.         /// </summary>
  1033.         private string GetPortCommandLine(FtpWebRequest request)
  1034.         {
  1035.             try {
  1036.                 // retrieves the IP address of the local endpoint
  1037.                 IPEndPoint localEP = (IPEndPoint)m_DataSocket.LocalEndPoint;
  1038.                 if (ServerAddress.AddressFamily == AddressFamily.InterNetwork) {
  1039.                     return FormatAddress(localEP.Address, localEP.Port);
  1040.                 }
  1041.                 else if (ServerAddress.AddressFamily == AddressFamily.InterNetworkV6) {
  1042.                     return FormatAddressV6(localEP.Address, localEP.Port);
  1043.                 }
  1044.                 else {
  1045.                     throw new InternalException();
  1046.                 }
  1047.             }
  1048.             catch (Exception e) {
  1049.                 throw GenerateException(WebExceptionStatus.ProtocolError, e);
  1050.                 // could not open data connection
  1051.             }
  1052.             catch {
  1053.                 throw GenerateException(WebExceptionStatus.ProtocolError, new Exception(SR.GetString(SR.net_nonClsCompliantException)));
  1054.                 // could not open data connection
  1055.             }
  1056.         }
  1057.        
  1058.         /// <summary>
  1059.         /// <para>Formats a simple FTP command + parameter in correct pre-wire format</para>
  1060.         /// </summary>
  1061.         private string FormatFtpCommand(string command, string parameter)
  1062.         {
  1063.                 /*size of ' ' \r\n*/            StringBuilder stringBuilder = new StringBuilder(command.Length + ((parameter != null) ? parameter.Length : 0) + 3);
  1064.             stringBuilder.Append(command);
  1065.             if (!ValidationHelper.IsBlankString(parameter)) {
  1066.                 stringBuilder.Append(' ');
  1067.                 stringBuilder.Append(parameter);
  1068.             }
  1069.             stringBuilder.Append("\r\n");
  1070.             return stringBuilder.ToString();
  1071.         }
  1072.        
  1073.        
  1074.         /// <devdoc>
  1075.         /// <para>
  1076.         /// This will handle either connecting to a port or listening for one
  1077.         /// </para>
  1078.         /// </devdoc>
  1079.         protected Socket CreateFtpDataSocket(FtpWebRequest request, Socket templateSocket)
  1080.         {
  1081.             // Safe to be called under an Assert.
  1082.             Socket socket = new Socket(templateSocket.AddressFamily, templateSocket.SocketType, templateSocket.ProtocolType);
  1083.             return socket;
  1084.         }
  1085.        
  1086.         /// <summary>
  1087.         /// This function is called by the GeneralWebRequest superclass to determine whether a response is valid, and when it is complete.
  1088.         /// It also gives the response description a
  1089.         /// </summary>
  1090.         protected override bool CheckValid(ResponseDescription response, ref int validThrough, ref int completeLength)
  1091.         {
  1092.             GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "CheckValid(" + response.StatusBuffer.ToString() + ")");
  1093.             // If the response is less than 4 bytes long, it is too short to tell, so return true, valid so far.
  1094.             if (response.StatusBuffer.Length < 4) {
  1095.                 return true;
  1096.             }
  1097.             string responseString = response.StatusBuffer.ToString();
  1098.            
  1099.             // Otherwise, if there is no status code for this response yet, get one.
  1100.             if (response.Status == ResponseDescription.NoStatus) {
  1101.                 // If the response does not start with three digits, then it is not a valid response from an FTP server.
  1102.                 if (!(Char.IsDigit(responseString[0]) && Char.IsDigit(responseString[1]) && Char.IsDigit(responseString[2]) && (responseString[3] == ' ' || responseString[3] == '-'))) {
  1103.                     return false;
  1104.                 }
  1105.                 else {
  1106.                     response.StatusCodeString = responseString.Substring(0, 3);
  1107.                     response.Status = Convert.ToInt16(response.StatusCodeString, NumberFormatInfo.InvariantInfo);
  1108.                 }
  1109.                
  1110.                 // IF a hyphen follows the status code on the first line of the response, then we have a multiline response coming.
  1111.                 if (responseString[3] == '-') {
  1112.                     response.Multiline = true;
  1113.                 }
  1114.             }
  1115.            
  1116.             // If a complete line of response has been received from the server, then see if the
  1117.             // overall response is complete.
  1118.             // If this was not a multiline response, then the response is complete at the end of the line.
  1119.            
  1120.             // If this was a multiline response (indicated by three digits followed by a '-' in the first line,
  1121.             // then we see if the last line received started with the same three digits followed by a space.
  1122.             // If it did, then this is the sign of a complete multiline response.
  1123.             // If the line contained three other digits followeed by the response, then this is a violation of the
  1124.             // FTP protocol for multiline responses.
  1125.             // All other cases indicate that the response is not yet complete.
  1126.             int index = 0;
  1127.             // gets the end line.
  1128.             while ((index = responseString.IndexOf("\r\n", validThrough)) != -1) {
  1129.                 int lineStart = validThrough;
  1130.                 validThrough = index + 2;
  1131.                 // validThrough now marks the end of the line being examined.
  1132.                 if (!response.Multiline) {
  1133.                     completeLength = validThrough;
  1134.                     return true;
  1135.                 }
  1136.                 // same here
  1137.                 if (responseString.Length > lineStart + 4) {
  1138.                     // if the first three characters of the the response line currently being examined
  1139.                     // match the status code, then if they are followed by a space, then we
  1140.                     // have reached the end of the reply.
  1141.                     if (responseString.Substring(lineStart, 3) == response.StatusCodeString) {
  1142.                         if (responseString[lineStart + 3] == ' ') {
  1143.                             completeLength = validThrough;
  1144.                             return true;
  1145.                         }
  1146.                     }
  1147.                 }
  1148.             }
  1149.             return true;
  1150.         }
  1151.        
  1152.         /// <summary>
  1153.         /// <para>Determnines whether the stream we return is Writeable or Readable</para>
  1154.         /// </summary>
  1155.         private TriState IsFtpDataStreamWriteable()
  1156.         {
  1157.             FtpWebRequest request = m_Request as FtpWebRequest;
  1158.             if (request != null) {
  1159.                 if (request.MethodInfo.IsUpload) {
  1160.                     return TriState.True;
  1161.                 }
  1162.                 else if (request.MethodInfo.IsDownload) {
  1163.                     return TriState.False;
  1164.                 }
  1165.             }
  1166.             return TriState.Unspecified;
  1167.         }
  1168.        
  1169.     }
  1170.     // class FtpControlStream
  1171. }
  1172. // namespace System.Net

Developer Fusion