New tests.
[mono.git] / mcs / class / System / System.Net / FtpWebRequest.cs
index 276e0c31b3aa6e0eaa1c5a197a83921aa402f8aa..f766fc29a50d88d7e00fc3b7482bee4cabdbe176 100644 (file)
@@ -6,27 +6,28 @@
 //
 // (c) Copyright 2006 Novell, Inc. (http://www.novell.com)
 //
+#if NET_2_0
 
 using System;
 using System.IO;
 using System.Net.Sockets;
 using System.Text;
 using System.Threading;
-#if NET_2_0
 using System.Net.Cache;
-#endif
-
-#if NET_2_0
+using System.Security.Cryptography.X509Certificates;
+using System.Net;
+using System.Net.Security;
+using System.Security.Authentication;
 
 namespace System.Net
 {
-       [Serializable]
-       public class FtpWebRequest : WebRequest
+       public sealed class FtpWebRequest : WebRequest
        {
                Uri requestUri;
+               string file_name; // By now, used for upload
                ServicePoint servicePoint;
-               Socket dataSocket;
-               NetworkStream controlStream;
+               Stream dataStream;
+               Stream controlStream;
                StreamReader controlReader;
                NetworkCredential credentials;
                IPHostEntry hostEntry;
@@ -34,28 +35,22 @@ namespace System.Net
                IWebProxy proxy;
                int timeout = 100000;
                int rwTimeout = 300000;
-               long offset;
+               long offset = 0;
                bool binary = true;
-               bool enableSsl;
-               bool requestInProgress;
+               bool enableSsl = false;
                bool usePassive = true;
-               bool keepAlive = true;
-               bool aborted;
-               bool transferCompleted;
-               bool gotRequestStream;
+               bool keepAlive = false;
                string method = WebRequestMethods.Ftp.DownloadFile;
                string renameTo;
                object locker = new object ();
-
-               FtpStatusCode statusCode;
-               string statusDescription = String.Empty;
-
-               FtpAsyncResult asyncRead;
-               FtpAsyncResult asyncWrite;
-
+               
+               RequestState requestState = RequestState.Before;
+               FtpAsyncResult asyncResult;
                FtpWebResponse ftpResponse;
-               Stream requestStream = Stream.Null;
+               Stream requestStream;
+               string initial_path;
 
+               const string ChangeDir = "CWD";
                const string UserCommand = "USER";
                const string PasswordCommand = "PASS";
                const string TypeCommand = "TYPE";
@@ -66,14 +61,28 @@ namespace System.Net
                const string RestCommand = "REST";
                const string RenameFromCommand = "RNFR";
                const string RenameToCommand = "RNTO";
+               const string QuitCommand = "QUIT";
                const string EOL = "\r\n"; // Special end of line
 
+               enum RequestState
+               {
+                       Before,
+                       Scheduled,
+                       Connecting,
+                       Authenticating,
+                       OpeningData,
+                       TransferInProgress,
+                       Finished,
+                       Aborted,
+                       Error
+               }
+
                // sorted commands
                static readonly string [] supportedCommands = new string [] {
                        WebRequestMethods.Ftp.AppendFile, // APPE
                        WebRequestMethods.Ftp.DeleteFile, // DELE
                        WebRequestMethods.Ftp.ListDirectoryDetails, // LIST
-                       WebRequestMethods.Ftp.GetDateTimestamps, // MDTM
+                       WebRequestMethods.Ftp.GetDateTimestamp, // MDTM
                        WebRequestMethods.Ftp.MakeDirectory, // MKD
                        WebRequestMethods.Ftp.ListDirectory, // NLST
                        WebRequestMethods.Ftp.PrintWorkingDirectory, // PWD
@@ -91,6 +100,33 @@ namespace System.Net
                        this.proxy = GlobalProxySelection.Select;
                }
 
+               static Exception GetMustImplement ()
+               {
+                       return new NotImplementedException ();
+               }
+               
+               [MonoTODO]
+               public X509CertificateCollection ClientCertificates
+               {
+                       get {
+                               throw GetMustImplement ();
+                       }
+                       set {
+                               throw GetMustImplement ();
+                       }
+               }
+               
+               [MonoTODO]
+               public override string ConnectionGroupName
+               {
+                       get {
+                               throw GetMustImplement ();
+                       }
+                       set {
+                               throw GetMustImplement ();
+                       }
+               }
+
                public override string ContentType {
                        get {
                                throw new NotSupportedException ();
@@ -137,6 +173,17 @@ namespace System.Net
                        }
                }
 
+               [MonoTODO]
+               public static new RequestCachePolicy DefaultCachePolicy
+               {
+                       get {
+                               throw GetMustImplement ();
+                       }
+                       set {
+                               throw GetMustImplement ();
+                       }
+               }
+
                public bool EnableSsl {
                        get {
                                return enableSsl;
@@ -147,13 +194,25 @@ namespace System.Net
                        }
                }
 
+               [MonoTODO]
+               public override WebHeaderCollection Headers
+               {
+                       get {
+                               throw GetMustImplement ();
+                       }
+                       set {
+                               throw GetMustImplement ();
+                       }
+               }
+
+               [MonoTODO ("We don't support KeepAlive = true")]
                public bool KeepAlive {
                        get {
                                return keepAlive;
                        }
                        set {
                                CheckRequestStarted ();
-                               keepAlive = value;
+                               //keepAlive = value;
                        }
                }
 
@@ -164,7 +223,7 @@ namespace System.Net
                        set {
                                CheckRequestStarted ();
                                if (value == null)
-                                       throw new ArgumentNullException ("method");
+                                       throw new ArgumentNullException ("Method string cannot be null");
 
                                if (value.Length == 0 || Array.BinarySearch (supportedCommands, value) < 0)
                                        throw new ArgumentException ("Method not supported", "value");
@@ -243,6 +302,17 @@ namespace System.Net
                                usePassive = value;
                        }
                }
+
+               [MonoTODO]
+               public override bool UseDefaultCredentials
+               {
+                       get {
+                               throw GetMustImplement ();
+                       }
+                       set {
+                               throw GetMustImplement ();
+                       }
+               }
                
                public bool UseBinary {
                        get {
@@ -273,360 +343,491 @@ namespace System.Net
                        }
                }
 
-               ServicePoint GetServicePoint ()
-               {
-                       if (servicePoint == null)
-                               servicePoint = ServicePointManager.FindServicePoint (requestUri, proxy);
+               RequestState State {
+                       get {
+                               lock (locker) {
+                                       return requestState;
+                               }
+                       }
 
-                       return servicePoint;
+                       set {
+                               lock (locker) {
+                                       CheckIfAborted ();
+                                       CheckFinalState ();
+                                       requestState = value;
+                               }
+                       }
                }
 
-               // Probably move some code of command connection here
-               bool ResolveHost ()
-               {
-                       hostEntry = GetServicePoint ().HostEntry;
-                       if (hostEntry == null)
-                               return false;
-                       
-                       return true;
-               }
+               public override void Abort () {
+                       lock (locker) {
+                               if (State == RequestState.TransferInProgress) {
+                                       /*FtpStatus status = */
+                                       SendCommand (false, AbortCommand);
+                               }
 
-               public override void Abort ()
-               {
-                       FtpStatusCode status = SendCommand (AbortCommand);
-                       if (status != FtpStatusCode.ClosingData)
-                               throw CreateExceptionFromResponse (); // Probably ignore it by now
+                               if (!InFinalState ()) {
+                                       State = RequestState.Aborted;
+                                       ftpResponse = new FtpWebResponse (this, requestUri, method, FtpStatusCode.FileActionAborted, "Aborted by request");
+                               }
+                       }
+               }
 
-                       aborted = true;
-                       if (asyncRead != null) {
-                               FtpAsyncResult r = asyncRead;
-                               WebException wexc = new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
-                               r.SetCompleted (false, wexc);
-                               r.DoCallback ();
-                               asyncRead = null;
+               public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state) {
+                       if (asyncResult != null && !asyncResult.IsCompleted) {
+                               throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
                        }
-                       if (asyncWrite != null) {
-                               FtpAsyncResult r = asyncWrite;
-                               WebException wexc = new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
-                               r.SetCompleted (false, wexc);
-                               r.DoCallback ();
-                               asyncWrite = null;
+
+                       CheckIfAborted ();
+                       
+                       asyncResult = new FtpAsyncResult (callback, state);
+
+                       lock (locker) {
+                               if (InFinalState ())
+                                       asyncResult.SetCompleted (true, ftpResponse);
+                               else {
+                                       if (State == RequestState.Before)
+                                               State = RequestState.Scheduled;
+
+                                       Thread thread = new Thread (ProcessRequest);
+                                       thread.Start ();
+                               }
                        }
+
+                       return asyncResult;
                }
 
-               void ProcessRequest ()
-               {
-                       ftpResponse = new FtpWebResponse (requestUri, method, keepAlive);
+               public override WebResponse EndGetResponse (IAsyncResult asyncResult) {
+                       if (asyncResult == null)
+                               throw new ArgumentNullException ("AsyncResult cannot be null!");
 
-                       if (!ResolveHost ()) {
-                               SetResponseError (new WebException ("The remote server name could not be resolved: " + requestUri,
-                                               null, WebExceptionStatus.NameResolutionFailure, ftpResponse));
-                               return;
-                       }
-                       
-                       if (!OpenControlConnection ())
-                               return;
+                       if (!(asyncResult is FtpAsyncResult) || asyncResult != this.asyncResult)
+                               throw new ArgumentException ("AsyncResult is from another request!");
 
-                       switch (method) {
-                               // Open data connection and receive data
-                               case WebRequestMethods.Ftp.DownloadFile:
-                               case WebRequestMethods.Ftp.ListDirectory:
-                               case WebRequestMethods.Ftp.ListDirectoryDetails:
-                                       DownloadData ();
-                                       break;
-                               // Open data connection and send data
-                               case WebRequestMethods.Ftp.AppendFile:
-                               case WebRequestMethods.Ftp.UploadFile:
-                               case WebRequestMethods.Ftp.UploadFileWithUniqueName:
-                                       UploadData ();
-                                       break;
-                               // Get info from control connection
-                               case WebRequestMethods.Ftp.GetFileSize:
-                               case WebRequestMethods.Ftp.GetDateTimestamps:
-                                       GetInfoFromControl ();
-                                       break;
-                               case WebRequestMethods.Ftp.Rename:
-                                       RenameFile ();
-                                       break;
-                               case WebRequestMethods.Ftp.MakeDirectory:
-                                       ProcessSimpleRequest ();
-                                       break;
-                               default: // What to do here?
-                                       throw new Exception ("Support for command not implemented yet");
-                       }
-               }
-
-               // Currently I use this only for MKD 
-               // (Commands that don't need any parsing in command connection
-               // for open data connection)
-               void ProcessSimpleRequest ()
-               {
-                       if (SendCommand (method, requestUri.LocalPath) != FtpStatusCode.PathnameCreated) {
-                               asyncRead.SetCompleted (true, CreateExceptionFromResponse ());
-                               return;
+                       FtpAsyncResult asyncFtpResult = (FtpAsyncResult) asyncResult;
+                       if (!asyncFtpResult.WaitUntilComplete (timeout, false)) {
+                               Abort ();
+                               throw new WebException ("Transfer timed out.", WebExceptionStatus.Timeout);
                        }
 
-                       asyncRead.SetCompleted (true, ftpResponse);
+                       CheckIfAborted ();
+
+                       asyncResult = null;
+
+                       if (asyncFtpResult.GotException)
+                               throw asyncFtpResult.Exception;
+
+                       return asyncFtpResult.Response;
                }
 
-               // It would be good to have a SetCompleted method for
-               // settting asyncRead as completed (some code is here and there, repeated)
-               void GetInfoFromControl ()
-               {
-                       FtpStatusCode status = SendCommand (method, requestUri.LocalPath);
-                       if (status != FtpStatusCode.FileStatus) {
-                               asyncRead.SetCompleted (true, CreateExceptionFromResponse ());
-                               return;
+               public override WebResponse GetResponse () {
+                       IAsyncResult asyncResult = BeginGetResponse (null, null);
+                       return EndGetResponse (asyncResult);
+               }
+
+               public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state) {
+                       if (method != WebRequestMethods.Ftp.UploadFile && method != WebRequestMethods.Ftp.UploadFileWithUniqueName &&
+                                       method != WebRequestMethods.Ftp.AppendFile)
+                               throw new ProtocolViolationException ();
+
+                       lock (locker) {
+                               CheckIfAborted ();
+
+                               if (State != RequestState.Before)
+                                       throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
+
+                               State = RequestState.Scheduled;
                        }
 
-                       string desc = statusDescription;
-                       Console.WriteLine ("Desc = " + desc);
-                       if (method == WebRequestMethods.Ftp.GetFileSize) {
-                               int i, len;
-                               long size;
-                               for (i = 4, len = 0; i < desc.Length && Char.IsDigit (desc [i]); i++, len++)
-                                       ;
+                       asyncResult = new FtpAsyncResult (callback, state);
+                       Thread thread = new Thread (ProcessRequest);
+                       thread.Start ();
 
-                               if (len == 0) {
-                                       asyncRead.SetCompleted (true, new WebException ("Bad format for server response in " + method));
-                                       return;
-                               }
+                       return asyncResult;
+               }
 
-                               if (!Int64.TryParse (desc.Substring (4, len), out size)) {
-                                       asyncRead.SetCompleted (true, new WebException ("Bad format for server response in " + method));
-                                       return;
-                               }
+               public override Stream EndGetRequestStream (IAsyncResult asyncResult) {
+                       if (asyncResult == null)
+                               throw new ArgumentNullException ("asyncResult");
 
-                               ftpResponse.contentLength = size;
-                               asyncRead.SetCompleted (true, ftpResponse);
-                               return;
+                       if (!(asyncResult is FtpAsyncResult))
+                               throw new ArgumentException ("asyncResult");
+
+                       if (State == RequestState.Aborted) {
+                               throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
                        }
                        
-                       if (method == WebRequestMethods.Ftp.GetDateTimestamps) {
-                               // Here parse the format the date time (different formats)
-                               asyncRead.SetCompleted (true, ftpResponse);
-                               return;
+                       if (asyncResult != this.asyncResult)
+                               throw new ArgumentException ("AsyncResult is from another request!");
+
+                       FtpAsyncResult res = (FtpAsyncResult) asyncResult;
+
+                       if (!res.WaitUntilComplete (timeout, false)) {
+                               Abort ();
+                               throw new WebException ("Request timed out");
                        }
 
-                       throw new Exception ("You shouldn't reach this point");
+                       if (res.GotException)
+                               throw res.Exception;
+
+                       return res.Stream;
                }
 
-               void RenameFile ()
+               public override Stream GetRequestStream () {
+                       IAsyncResult asyncResult = BeginGetRequestStream (null, null);
+                       return EndGetRequestStream (asyncResult);
+               }
+               
+               ServicePoint GetServicePoint ()
                {
-                       FtpStatusCode status = SendCommand (RenameFromCommand, requestUri.LocalPath);
-                       if (status == FtpStatusCode.FileCommandPending) {
-                               // Pass an empty string if RenameTo wasn't specified
-                               status = SendCommand (RenameToCommand, renameTo != null ? renameTo : String.Empty);
-                               
-                               if (status == FtpStatusCode.FileActionOK) {
-                                       ftpResponse.UpdateStatus (statusCode, statusDescription);
-                                       asyncRead.SetCompleted (true, ftpResponse);
-                                       return;
-                               }
-                       }
+                       if (servicePoint == null)
+                               servicePoint = ServicePointManager.FindServicePoint (requestUri, proxy);
 
-                       ftpResponse.UpdateStatus (statusCode, statusDescription);
-                       asyncRead.SetCompleted (true, CreateExceptionFromResponse ());
+                       return servicePoint;
                }
 
-               void UploadData ()
+               // Probably move some code of command connection here
+               void ResolveHost ()
                {
-                       if (gotRequestStream) {
-                               if (GetResponseCode () != FtpStatusCode.ClosingData)
-                                       asyncRead.SetCompleted (true, CreateExceptionFromResponse ());
-                               
-                               return;
-                       }
-                       
-                       if (!OpenDataConnection ())
-                               return;
+                       CheckIfAborted ();
+                       hostEntry = GetServicePoint ().HostEntry;
 
-                       gotRequestStream = true;
-                       requestStream = new FtpDataStream (this, dataSocket, false);
-                       asyncWrite.SetCompleted (true, requestStream);
+                       if (hostEntry == null) {
+                               ftpResponse.UpdateStatus (new FtpStatus(FtpStatusCode.ActionAbortedLocalProcessingError, "Cannot resolve server name"));
+                               throw new WebException ("The remote server name could not be resolved: " + requestUri,
+                                       null, WebExceptionStatus.NameResolutionFailure, ftpResponse);
+                       }
                }
 
-               void DownloadData ()
-               {
-                       FtpStatusCode status;
+               void ProcessRequest () {
 
-                       // Handle content offset
-                       if (offset > 0) {
-                               status = SendCommand (RestCommand, offset.ToString ());
-                               if (status != FtpStatusCode.FileCommandPending) {
-                                       asyncRead.SetCompleted (true, CreateExceptionFromResponse ());
-                                       return;
+                       if (State == RequestState.Scheduled) {
+                               ftpResponse = new FtpWebResponse (this, requestUri, method, keepAlive);
+
+                               try {
+                                       ProcessMethod ();
+                                       //State = RequestState.Finished;
+                                       //finalResponse = ftpResponse;
+                                       asyncResult.SetCompleted (false, ftpResponse);
+                               }
+                               catch (Exception e) {
+                                       State = RequestState.Error;
+                                       SetCompleteWithError (e);
                                }
                        }
+                       else {
+                               if (InProgress ()) {
+                                       FtpStatus status = GetResponseStatus ();
 
-                       if (!OpenDataConnection ())
-                               return;
+                                       ftpResponse.UpdateStatus (status);
+
+                                       if (ftpResponse.IsFinal ()) {
+                                               State = RequestState.Finished;
+                                       }
+                               }
 
-                       ftpResponse.Stream = new FtpDataStream (this, dataSocket, true);
-                       ftpResponse.StatusDescription = statusDescription;
-                       ftpResponse.StatusCode = statusCode;
-                       asyncRead.SetCompleted (true, ftpResponse);
+                               asyncResult.SetCompleted (false, ftpResponse);
+                       }
                }
 
-               public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state)
+               void SetType ()
                {
-                       if (aborted)
-                               throw new WebException ("Request was previously aborted.");
-                       
-                       Monitor.Enter (this);
-                       if (asyncRead != null) {
-                               Monitor.Exit (this);
-                               throw new InvalidOperationException ();
+                       if (binary) {
+                               FtpStatus status = SendCommand (TypeCommand, DataType);
+                               if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
+                                       throw CreateExceptionFromResponse (status);
                        }
+               }
 
-                       requestInProgress = true;
-                       asyncRead = new FtpAsyncResult (callback, state);
-                       Thread thread = new Thread (ProcessRequest);
-                       thread.Start ();
+               string GetRemoteFolderPath (Uri uri)
+               {
+                       string result;
+                       string local_path = Uri.UnescapeDataString (uri.LocalPath);
+                       if (initial_path == null || initial_path == "/") {
+                               result = local_path;
+                       } else {
+                               if (local_path [0] == '/')
+                                       local_path = local_path.Substring (1);
+
+                               Uri initial = new Uri ("ftp://dummy-host" + initial_path);
+                               result = new Uri (initial, local_path).LocalPath;
+                       }
 
-                       Monitor.Exit (this);
-                       return asyncRead;
+                       int last = result.LastIndexOf ('/');
+                       if (last == -1)
+                               return null;
+
+                       return result.Substring (0, last + 1);
                }
 
-               public override WebResponse EndGetResponse (IAsyncResult asyncResult)
+               void CWDAndSetFileName (Uri uri)
                {
-                       if (asyncResult == null)
-                               throw new ArgumentNullException ("asyncResult");
+                       string remote_folder = GetRemoteFolderPath (uri);
+                       FtpStatus status;
+                       if (remote_folder != null) {
+                               status = SendCommand (ChangeDir, remote_folder);
+                               if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
+                                       throw CreateExceptionFromResponse (status);
+
+                               int last = uri.LocalPath.LastIndexOf ('/');
+                               if (last >= 0) {
+                                       file_name = Uri.UnescapeDataString (uri.LocalPath.Substring (last + 1));
+                               }
+                       }
+               }
 
-                       if (!(asyncResult is FtpAsyncResult) || asyncResult != asyncRead)
-                               throw new ArgumentException ("asyncResult");
+               void ProcessMethod ()
+               {
+                       State = RequestState.Connecting;
 
-                       FtpAsyncResult asyncFtpResult = (FtpAsyncResult) asyncResult;
-                       if (!asyncFtpResult.WaitUntilComplete (timeout, false)) {
-                               Abort ();
-                               throw new WebException ("Transfer timed out.", WebExceptionStatus.Timeout);
+                       ResolveHost ();
+
+                       OpenControlConnection ();
+                       CWDAndSetFileName (requestUri);
+                       SetType ();
+
+                       switch (method) {
+                       // Open data connection and receive data
+                       case WebRequestMethods.Ftp.DownloadFile:
+                       case WebRequestMethods.Ftp.ListDirectory:
+                       case WebRequestMethods.Ftp.ListDirectoryDetails:
+                               DownloadData ();
+                               break;
+                       // Open data connection and send data
+                       case WebRequestMethods.Ftp.AppendFile:
+                       case WebRequestMethods.Ftp.UploadFile:
+                       case WebRequestMethods.Ftp.UploadFileWithUniqueName:
+                               UploadData ();
+                               break;
+                       // Get info from control connection
+                       case WebRequestMethods.Ftp.GetFileSize:
+                       case WebRequestMethods.Ftp.GetDateTimestamp:
+                       case WebRequestMethods.Ftp.PrintWorkingDirectory:
+                       case WebRequestMethods.Ftp.MakeDirectory:
+                       case WebRequestMethods.Ftp.Rename:
+                       case WebRequestMethods.Ftp.DeleteFile:
+                               ProcessSimpleMethod ();
+                               break;
+                       default: // What to do here?
+                               throw new Exception (String.Format ("Support for command {0} not implemented yet", method));
                        }
 
-                       if (asyncFtpResult.GotException)
-                               throw asyncFtpResult.Exception;
+                       CheckIfAborted ();
+               }
 
-                       return asyncFtpResult.Response;
+               private void CloseControlConnection () {
+                       if (controlStream != null) {
+                               SendCommand (QuitCommand);
+                               controlStream.Close ();
+                               controlStream = null;
+                       }
                }
 
-               public override WebResponse GetResponse ()
-               {
-                       IAsyncResult asyncResult = BeginGetResponse (null, null);
-                       return EndGetResponse (asyncResult);
+               private void CloseDataConnection () {
+                       if(dataStream != null) {
+                               dataStream.Close ();
+                               dataStream = null;
+                       }
                }
 
-               public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state)
+               private void CloseConnection () {
+                       CloseControlConnection ();
+                       CloseDataConnection ();
+               }
+               
+               void ProcessSimpleMethod ()
                {
-                       if (aborted)
-                               throw new WebException ("Request was previously aborted.");
+                       State = RequestState.TransferInProgress;
                        
-                       if (method != WebRequestMethods.Ftp.UploadFile && method != WebRequestMethods.Ftp.UploadFileWithUniqueName &&
-                                       method != WebRequestMethods.Ftp.AppendFile)
-                               throw new ProtocolViolationException ();
+                       FtpStatus status;
+                       
+                       if (method == WebRequestMethods.Ftp.PrintWorkingDirectory)
+                               method = "PWD";
 
-                       lock (locker) {
-                               if (asyncWrite != null || asyncRead != null)
-                                       throw new InvalidOperationException ();
-                               
-                               requestInProgress = true;
-                               asyncWrite = new FtpAsyncResult (callback, state);
-                               Thread thread = new Thread (ProcessRequest);
-                               thread.Start ();
+                       if (method == WebRequestMethods.Ftp.Rename)
+                               method = RenameFromCommand;
+                       
+                       status = SendCommand (method, file_name);
 
-                               return asyncWrite;
-                       }
-               }
+                       ftpResponse.Stream = Stream.Null;
+                       
+                       string desc = status.StatusDescription;
 
-               public override Stream EndGetRequestStream (IAsyncResult asyncResult)
-               {
-                       if (asyncResult == null)
-                               throw new ArgumentNullException ("asyncResult");
+                       switch (method) {
+                       case WebRequestMethods.Ftp.GetFileSize: {
+                                       if (status.StatusCode != FtpStatusCode.FileStatus)
+                                               throw CreateExceptionFromResponse (status);
 
-                       if (!(asyncResult is FtpAsyncResult))
-                               throw new ArgumentException ("asyncResult");
+                                       int i, len;
+                                       long size;
+                                       for (i = 4, len = 0; i < desc.Length && Char.IsDigit (desc [i]); i++, len++)
+                                               ;
 
-                       FtpAsyncResult res = (FtpAsyncResult) asyncResult;
-                       if (!res.WaitUntilComplete (timeout, false)) {
-                               Abort ();
-                               throw new WebException ("Request timeod out");
+                                       if (len == 0)
+                                               throw new WebException ("Bad format for server response in " + method);
+
+                                       if (!Int64.TryParse (desc.Substring (4, len), out size))
+                                               throw new WebException ("Bad format for server response in " + method);
+
+                                       ftpResponse.contentLength = size;
+                               }
+                               break;
+                       case WebRequestMethods.Ftp.GetDateTimestamp:
+                               if (status.StatusCode != FtpStatusCode.FileStatus)
+                                       throw CreateExceptionFromResponse (status);
+                               ftpResponse.LastModified = DateTime.ParseExact (desc.Substring (4), "yyyyMMddHHmmss", null);
+                               break;
+                       case WebRequestMethods.Ftp.MakeDirectory:
+                               if (status.StatusCode != FtpStatusCode.PathnameCreated)
+                                       throw CreateExceptionFromResponse (status);
+                               break;
+                       case ChangeDir:
+                               method = WebRequestMethods.Ftp.PrintWorkingDirectory;
+
+                               if (status.StatusCode != FtpStatusCode.FileActionOK)
+                                       throw CreateExceptionFromResponse (status);
+
+                               status = SendCommand (method);
+
+                               if (status.StatusCode != FtpStatusCode.PathnameCreated)
+                                       throw CreateExceptionFromResponse (status);
+                               break;
+                       case RenameFromCommand:
+                               method = WebRequestMethods.Ftp.Rename;
+                               if (status.StatusCode != FtpStatusCode.FileCommandPending) 
+                                       throw CreateExceptionFromResponse (status);
+                               // Pass an empty string if RenameTo wasn't specified
+                               status = SendCommand (RenameToCommand, renameTo != null ? renameTo : String.Empty);
+                               if (status.StatusCode != FtpStatusCode.FileActionOK)
+                                       throw CreateExceptionFromResponse (status);
+                               break;
+                       case WebRequestMethods.Ftp.DeleteFile:
+                               if (status.StatusCode != FtpStatusCode.FileActionOK)  {
+                                       throw CreateExceptionFromResponse (status);
+                               }
+                               break;
                        }
 
-                       if (res.GotException)
-                               throw res.Exception;
+                       State = RequestState.Finished;
+               }
 
-                       return res.Stream;
+               void UploadData ()
+               {
+                       State = RequestState.OpeningData;
+
+                       OpenDataConnection ();
+
+                       State = RequestState.TransferInProgress;
+                       requestStream = new FtpDataStream (this, dataStream, false);
+                       asyncResult.Stream = requestStream;
                }
 
-               public override Stream GetRequestStream ()
+               void DownloadData ()
                {
-                       IAsyncResult asyncResult = BeginGetRequestStream (null, null);
-                       return EndGetRequestStream (asyncResult);
+                       State = RequestState.OpeningData;
+
+                       OpenDataConnection ();
+
+                       State = RequestState.TransferInProgress;
+                       ftpResponse.Stream = new FtpDataStream (this, dataStream, true);
                }
 
                void CheckRequestStarted ()
                {
-                       if (requestInProgress)
-                               throw new InvalidOperationException ("request in progress");
+                       if (State != RequestState.Before)
+                               throw new InvalidOperationException ("There is a request currently in progress");
                }
 
-               bool OpenControlConnection ()
+               void OpenControlConnection ()
                {
+                       Exception exception = null;
                        Socket sock = null;
                        foreach (IPAddress address in hostEntry.AddressList) {
                                sock = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
-                               try {
-                                       sock.Connect (new IPEndPoint (address, requestUri.Port));
-                                       localEndPoint = (IPEndPoint) sock.LocalEndPoint;
-                                       break;
-                               } catch (SocketException e) {
+
+                               IPEndPoint remote = new IPEndPoint (address, requestUri.Port);
+
+                               if (!ServicePoint.CallEndPointDelegate (sock, remote)) {
                                        sock.Close ();
                                        sock = null;
+                               } else {
+                                       try {
+                                               sock.Connect (remote);
+                                               localEndPoint = (IPEndPoint) sock.LocalEndPoint;
+                                               break;
+                                       } catch (SocketException exc) {
+                                               exception = exc;
+                                               sock.Close ();
+                                               sock = null;
+                                       }
                                }
                        }
 
                        // Couldn't connect to any address
-                       if (sock == null) {
-                               SetResponseError (new WebException ("Unable to connect to remote server", null, 
-                                               WebExceptionStatus.UnknownError, ftpResponse));
-                               return false;
-                       }
+                       if (sock == null)
+                               throw new WebException ("Unable to connect to remote server", exception,
+                                               WebExceptionStatus.UnknownError, ftpResponse);
 
                        controlStream = new NetworkStream (sock);
                        controlReader = new StreamReader (controlStream, Encoding.ASCII);
 
-                       if (!Authenticate ()) {
-                               SetResponseError (CreateExceptionFromResponse ());
-                               return false;
+                       State = RequestState.Authenticating;
+
+                       Authenticate ();
+                       FtpStatus status = SendCommand ("OPTS", "utf8", "on");
+                       // ignore status for OPTS
+                       status = SendCommand (WebRequestMethods.Ftp.PrintWorkingDirectory);
+                       initial_path = GetInitialPath (status);
+               }
+
+               static string GetInitialPath (FtpStatus status)
+               {
+                       int s = (int) status.StatusCode;
+                       if (s < 200 || s > 300 || status.StatusDescription.Length <= 4)
+                               throw new WebException ("Error getting current directory: " + status.StatusDescription, null,
+                                               WebExceptionStatus.UnknownError, null);
+
+                       string msg = status.StatusDescription.Substring (4);
+                       if (msg [0] == '"') {
+                               int next_quote = msg.IndexOf ('\"', 1);
+                               if (next_quote == -1)
+                                       throw new WebException ("Error getting current directory: PWD -> " + status.StatusDescription, null,
+                                                               WebExceptionStatus.UnknownError, null);
+
+                               msg = msg.Substring (1, next_quote - 1);
                        }
 
-                       return true;
+                       if (!msg.EndsWith ("/"))
+                               msg += "/";
+                       return msg;
                }
 
                // Probably we could do better having here a regex
-               Socket SetupPassiveConnection ()
+               Socket SetupPassiveConnection (string statusDescription)
                {
                        // Current response string
                        string response = statusDescription;
                        if (response.Length < 4)
-                               return null;
+                               throw new WebException ("Cannot open passive data connection");
                        
                        // Look for first digit after code
                        int i;
                        for (i = 3; i < response.Length && !Char.IsDigit (response [i]); i++)
                                ;
                        if (i >= response.Length)
-                               return null;
+                               throw new WebException ("Cannot open passive data connection");
 
                        // Get six elements
                        string [] digits = response.Substring (i).Split (new char [] {','}, 6);
                        if (digits.Length != 6)
-                               return null;
+                               throw new WebException ("Cannot open passive data connection");
 
                        // Clean non-digits at the end of last element
                        int j;
                        for (j = digits [5].Length - 1; j >= 0 && !Char.IsDigit (digits [5][j]); j--)
                                ;
                        if (j < 0)
-                               return null;
+                               throw new WebException ("Cannot open passive data connection");
                        
                        digits [5] = digits [5].Substring (0, j + 1);
 
@@ -634,77 +835,77 @@ namespace System.Net
                        try {
                                ip = IPAddress.Parse (String.Join (".", digits, 0, 4));
                        } catch (FormatException) {
-                               return null;
+                               throw new WebException ("Cannot open passive data connection");
                        }
 
                        // Get the port
                        int p1, p2, port;
                        if (!Int32.TryParse (digits [4], out p1) || !Int32.TryParse (digits [5], out p2))
-                               return null;
+                               throw new WebException ("Cannot open passive data connection");
 
                        port = (p1 << 8) + p2; // p1 * 256 + p2
                        //port = p1 * 256 + p2;
                        if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
-                               return null;
+                               throw new WebException ("Cannot open passive data connection");
 
                        IPEndPoint ep = new IPEndPoint (ip, port);
                        Socket sock = new Socket (ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                        try {
                                sock.Connect (ep);
-                       } catch (SocketException exc) {
+                       } catch (SocketException) {
                                sock.Close ();
-                               return null;
+                               throw new WebException ("Cannot open passive data connection");
                        }
 
                        return sock;
                }
 
-               Exception CreateExceptionFromResponse ()
+               Exception CreateExceptionFromResponse (FtpStatus status)
                {
-                       WebException exc = new WebException ("Server returned an error: " + statusDescription, null, 
-                                       WebExceptionStatus.ProtocolError, ftpResponse);
+                       FtpWebResponse ftpResponse = new FtpWebResponse (this, requestUri, method, status);
+                       
+                       WebException exc = new WebException ("Server returned an error: " + status.StatusDescription, 
+                               null, WebExceptionStatus.ProtocolError, ftpResponse);
                        return exc;
                }
                
                // Here we could also get a server error, so be cautious
                internal void SetTransferCompleted ()
                {
-                       if (transferCompleted)
+                       if (InFinalState ())
                                return;
-                       
-                       transferCompleted = true;
-                       
-                       FtpStatusCode status = GetResponseCode ();
-                       ftpResponse.StatusCode = status;
-                       ftpResponse.StatusDescription = statusDescription;
+
+                       State = RequestState.Finished;
+                       FtpStatus status = GetResponseStatus ();
+                       ftpResponse.UpdateStatus (status);
+                       if(!keepAlive)
+                               CloseConnection ();
                }
 
-               internal void SetResponseError (Exception exc)
+               internal void OperationCompleted ()
                {
-                       FtpAsyncResult ar = asyncRead;
-                       if (ar == null)
-                               ar = asyncWrite;
+                       if(!keepAlive)
+                               CloseConnection ();
+               }
 
-                       ar.SetCompleted (true, exc);
-                       ar.DoCallback ();
+               void SetCompleteWithError (Exception exc)
+               {
+                       if (asyncResult != null) {
+                               asyncResult.SetCompleted (false, exc);
+                       }
                }
 
                Socket InitDataConnection ()
                {
-                       FtpStatusCode status;
+                       FtpStatus status;
                        
                        if (usePassive) {
                                status = SendCommand (PassiveCommand);
-                               if (status != FtpStatusCode.EnteringPassive) {
-                                       SetResponseError (CreateExceptionFromResponse ());
-                                       return null;
+                               if (status.StatusCode != FtpStatusCode.EnteringPassive) {
+                                       throw CreateExceptionFromResponse (status);
                                }
                                
-                               Socket retval = SetupPassiveConnection ();
-                               if (retval == null)
-                                       SetResponseError (new WebException ("Couldn't setup passive connection"));
-                                       
-                               return retval;
+                               return SetupPassiveConnection (status.StatusDescription);
                        }
 
                        // Open a socket to listen the server's connection
@@ -716,115 +917,142 @@ namespace System.Net
                        } catch (SocketException e) {
                                sock.Close ();
 
-                               SetResponseError (new WebException ("Couldn't open listening socket on client", e));
-                               return null;
+                               throw new WebException ("Couldn't open listening socket on client", e);
                        }
 
                        IPEndPoint ep = (IPEndPoint) sock.LocalEndPoint;
-                       string ipString = ep.Address.ToString ().Replace (".", ",");
+                       string ipString = ep.Address.ToString ().Replace ('.', ',');
                        int h1 = ep.Port >> 8; // ep.Port / 256
                        int h2 = ep.Port % 256;
 
                        string portParam = ipString + "," + h1 + "," + h2;
                        status = SendCommand (PortCommand, portParam);
-                       if (status != FtpStatusCode.CommandOK) {
+                       
+                       if (status.StatusCode != FtpStatusCode.CommandOK) {
                                sock.Close ();
-                               
-                               SetResponseError (CreateExceptionFromResponse ());
-                               return null;
+                               throw (CreateExceptionFromResponse (status));
                        }
 
                        return sock;
                }
 
-               bool OpenDataConnection ()
+               void OpenDataConnection ()
                {
-                       FtpStatusCode status;
+                       FtpStatus status;
+                       
                        Socket s = InitDataConnection ();
-                       if (s == null)
-                               return false;
 
-                       // TODO - Check that this command is only used for data connection based commands
-                       if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails) {
-                               status = SendCommand (TypeCommand, DataType);
-                               
-                               if (status != FtpStatusCode.CommandOK) {
-                                       SetResponseError (CreateExceptionFromResponse ());
-                                       return false;
-                               }
+                       // Handle content offset
+                       if (offset > 0) {
+                               status = SendCommand (RestCommand, offset.ToString ());
+                               if (status.StatusCode != FtpStatusCode.FileCommandPending)
+                                       throw CreateExceptionFromResponse (status);
                        }
 
-                       status = SendCommand (method, requestUri.LocalPath);
-                       if (status != FtpStatusCode.OpeningData) {
-                               SetResponseError (CreateExceptionFromResponse ());
-                               return false;
+                       if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails &&
+                           method != WebRequestMethods.Ftp.UploadFileWithUniqueName) {
+                               status = SendCommand (method, file_name);
+                       } else {
+                               status = SendCommand (method);
                        }
-                       
+
+                       if (status.StatusCode != FtpStatusCode.OpeningData && status.StatusCode != FtpStatusCode.DataAlreadyOpen)
+                               throw CreateExceptionFromResponse (status);
+
                        if (usePassive) {
-                               dataSocket = s;
-                               return true;
+                               dataStream = new NetworkStream (s, false);
+                               if (EnableSsl)
+                                       ChangeToSSLSocket (ref dataStream);
                        }
+                       else {
+
+                               // Active connection (use Socket.Blocking to true)
+                               Socket incoming = null;
+                               try {
+                                       incoming = s.Accept ();
+                               }
+                               catch (SocketException) {
+                                       s.Close ();
+                                       if (incoming != null)
+                                               incoming.Close ();
+
+                                       throw new ProtocolViolationException ("Server commited a protocol violation.");
+                               }
 
-                       // Active connection (use Socket.Blocking to true)
-                       Socket incoming = null;
-                       try {
-                               incoming = s.Accept ();
-                       } catch (SocketException e) {
                                s.Close ();
-                               if (incoming != null)
-                                       incoming.Close ();
-                               
-                               SetResponseError (new ProtocolViolationException ("Server commited a protocol violation."));
-                               return false;
-                       } 
+                               dataStream = new NetworkStream (incoming, false);
+                               if (EnableSsl)
+                                       ChangeToSSLSocket (ref dataStream);
+                       }
 
-                       s.Close ();
-                       dataSocket = incoming;
-                       return true;
+                       ftpResponse.UpdateStatus (status);
                }
 
-               // Take in count 'account' case
-               bool Authenticate ()
+               void Authenticate ()
                {
                        string username = null;
                        string password = null;
-                       
+                       string domain = null;
+
                        if (credentials != null) {
                                username = credentials.UserName;
                                password = credentials.Password;
-                               // account = credentials.Domain;
+                               domain = credentials.Domain;
                        }
 
                        if (username == null)
                                username = "anonymous";
                        if (password == null)
                                password = "@anonymous";
+                       if (!string.IsNullOrEmpty (domain))
+                               username = domain + '\\' + username;
 
                        // Connect to server and get banner message
-                       FtpStatusCode status = GetResponseCode ();
-                       ftpResponse.BannerMessage = statusDescription;
-                       if (status != FtpStatusCode.SendUserCommand)
-                               return false;
+                       FtpStatus status = GetResponseStatus ();
+                       ftpResponse.BannerMessage = status.StatusDescription;
+
+                       if (EnableSsl) {
+                               InitiateSecureConnection (ref controlStream);
+                               controlReader = new StreamReader (controlStream, Encoding.ASCII);
+                               status = SendCommand ("PBSZ", "0");
+                               int st = (int) status.StatusCode;
+                               if (st < 200 || st >= 300)
+                                       throw CreateExceptionFromResponse (status);
+                               // TODO: what if "PROT P" is denied by the server? What does MS do?
+                               status = SendCommand ("PROT", "P");
+                               st = (int) status.StatusCode;
+                               if (st < 200 || st >= 300)
+                                       throw CreateExceptionFromResponse (status);
+
+                               status = new FtpStatus (FtpStatusCode.SendUserCommand, "");
+                       }
+                       
+                       if (status.StatusCode != FtpStatusCode.SendUserCommand)
+                               throw CreateExceptionFromResponse (status);
 
                        status = SendCommand (UserCommand, username);
-                       if (status == FtpStatusCode.LoggedInProceed) {
-                               ftpResponse.WelcomeMessage = statusDescription;
-                               return true;
-                       }
-                       if (status == FtpStatusCode.SendPasswordCommand) {
-                               status = SendCommand (PasswordCommand, password);
-                               if (status == FtpStatusCode.LoggedInProceed) {
-                                       ftpResponse.WelcomeMessage = statusDescription;
-                                       return true;
-                               }
 
-                               return false;
+                       switch (status.StatusCode) {
+                       case FtpStatusCode.SendPasswordCommand:
+                               status = SendCommand (PasswordCommand, password);
+                               if (status.StatusCode != FtpStatusCode.LoggedInProceed)
+                                       throw CreateExceptionFromResponse (status);
+                               break;
+                       case FtpStatusCode.LoggedInProceed:
+                               break;
+                       default:
+                               throw CreateExceptionFromResponse (status);
                        }
 
-                       return false;
+                       ftpResponse.WelcomeMessage = status.StatusDescription;
+                       ftpResponse.UpdateStatus (status);
+               }
+
+               FtpStatus SendCommand (string command, params string [] parameters) {
+                       return SendCommand (true, command, parameters);
                }
 
-               FtpStatusCode SendCommand (string command, params string [] parameters)
+               FtpStatus SendCommand (bool waitResponse, string command, params string [] parameters)
                {
                        byte [] cmd;
                        string commandString = command;
@@ -837,33 +1065,117 @@ namespace System.Net
                                controlStream.Write (cmd, 0, cmd.Length);
                        } catch (IOException) {
                                //controlStream.Close ();
-                               return FtpStatusCode.ServiceNotAvalaible;
+                               return new FtpStatus(FtpStatusCode.ServiceNotAvailable, "Write failed");
                        }
 
-                       return GetResponseCode ();
+                       if(!waitResponse)
+                               return null;
+                       
+                       FtpStatus result = GetResponseStatus ();
+                       if (ftpResponse != null)
+                               ftpResponse.UpdateStatus (result);
+                       return result;
                }
 
-               internal FtpStatusCode GetResponseCode ()
+               internal static FtpStatus ServiceNotAvailable ()
                {
-                       string responseString = null;
-                       try {
-                               responseString = controlReader.ReadLine ();
-                       } catch (IOException exc) {
-                               // controlReader.Close ();
+                       return new FtpStatus (FtpStatusCode.ServiceNotAvailable, Locale.GetText ("Invalid response from server"));
+               }
+               
+               internal FtpStatus GetResponseStatus ()
+               {
+                       while (true) {
+                               string response = null;
+
+                               try {
+                                       response = controlReader.ReadLine ();
+                               } catch (IOException) {
+                               }
+
+                               if (response == null || response.Length < 3)
+                                       return ServiceNotAvailable ();
+
+                               int code;
+                               if (!Int32.TryParse (response.Substring (0, 3), out code))
+                                       return ServiceNotAvailable ();
+
+                               if (response.Length > 3 && response [3] == '-'){
+                                       string line = null;
+                                       string find = code.ToString() + ' ';
+                                       while (true){
+                                               try {
+                                                       line = controlReader.ReadLine();
+                                               } catch (IOException) {
+                                               }
+                                               if (line == null)
+                                                       return ServiceNotAvailable ();
+                                               
+                                               response += Environment.NewLine + line;
+
+                                               if (line.StartsWith(find, StringComparison.Ordinal))
+                                                       break;
+                                       } 
+                               }
+                               return new FtpStatus ((FtpStatusCode) code, response);
                        }
+               }
+
+               private void InitiateSecureConnection (ref Stream stream) {
+                       FtpStatus status = SendCommand (AuthCommand, "TLS");
+                       if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession)
+                               throw CreateExceptionFromResponse (status);
+
+                       ChangeToSSLSocket (ref stream);
+               }
+
+#if SECURITY_DEP
+               RemoteCertificateValidationCallback callback = delegate (object sender,
+                                                                        X509Certificate certificate,
+                                                                        X509Chain chain,
+                                                                        SslPolicyErrors sslPolicyErrors) {
+                       // honor any exciting callback defined on ServicePointManager
+                       if (ServicePointManager.ServerCertificateValidationCallback != null)
+                               return ServicePointManager.ServerCertificateValidationCallback (sender, certificate, chain, sslPolicyErrors);
+                       // otherwise provide our own
+                       if (sslPolicyErrors != SslPolicyErrors.None)
+                               throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors);
+                       return true;
+                       };
+#endif
 
-                       if (responseString == null || responseString.Length < 3)
-                               return FtpStatusCode.ServiceNotAvalaible;
+               internal bool ChangeToSSLSocket (ref Stream stream) {
+#if TARGET_JVM
+                       stream.ChangeToSSLSocket ();
+                       return true;
+#elif SECURITY_DEP
+                       SslStream sslStream = new SslStream (stream, true, callback, null);
+                       //sslStream.AuthenticateAsClient (Host, this.ClientCertificates, SslProtocols.Default, false);
+                       //TODO: client certificates
+                       sslStream.AuthenticateAsClient (requestUri.Host, null, SslProtocols.Default, false);
+                       stream = sslStream;
+                       return true;
+#else
+                       throw new NotImplementedException ();
+#endif
+               }
+               
+               bool InFinalState () {
+                       return (State == RequestState.Aborted || State == RequestState.Error || State == RequestState.Finished);
+               }
 
-                       string codeString = responseString.Substring (0, 3);
-                       int code;
-                       if (!Int32.TryParse (codeString, out code))
-                               return FtpStatusCode.ServiceNotAvalaible;
+               bool InProgress () {
+                       return (State != RequestState.Before && !InFinalState ());
+               }
 
-                       statusDescription = responseString;
-                       return statusCode = (FtpStatusCode) code;
+               internal void CheckIfAborted () {
+                       if (State == RequestState.Aborted)
+                               throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
                }
 
+               void CheckFinalState () {
+                       if (InFinalState ())
+                               throw new InvalidOperationException ("Cannot change final state");
+               }
        }
 }