X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FSystem%2FSystem.Net%2FFtpWebRequest.cs;h=2a23fb94d910dacb62b8deb7cca30ab61f03691e;hb=86d332be05580366e264fb712178ef44d38c6c8b;hp=276e0c31b3aa6e0eaa1c5a197a83921aa402f8aa;hpb=9869ae24b88761ab261c4311e24f7383b4af3f02;p=mono.git diff --git a/mcs/class/System/System.Net/FtpWebRequest.cs b/mcs/class/System/System.Net/FtpWebRequest.cs index 276e0c31b3a..2a23fb94d91 100644 --- a/mcs/class/System/System.Net/FtpWebRequest.cs +++ b/mcs/class/System/System.Net/FtpWebRequest.cs @@ -6,24 +6,24 @@ // // (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 +using System.Security.Cryptography.X509Certificates; +using System.Net; -#if NET_2_0 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; @@ -34,28 +34,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 +60,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 +99,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 +172,17 @@ namespace System.Net } } + [MonoTODO] + public static new RequestCachePolicy DefaultCachePolicy + { + get { + throw GetMustImplement (); + } + set { + throw GetMustImplement (); + } + } + public bool EnableSsl { get { return enableSsl; @@ -147,6 +193,17 @@ namespace System.Net } } + [MonoTODO] + public override WebHeaderCollection Headers + { + get { + throw GetMustImplement (); + } + set { + throw GetMustImplement (); + } + } + public bool KeepAlive { get { return keepAlive; @@ -164,7 +221,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 +300,17 @@ namespace System.Net usePassive = value; } } + + [MonoTODO] + public override bool UseDefaultCredentials + { + get { + throw GetMustImplement (); + } + set { + throw GetMustImplement (); + } + } public bool UseBinary { get { @@ -273,360 +341,485 @@ 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 (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 (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); - ftpResponse.Stream = new FtpDataStream (this, dataSocket, true); - ftpResponse.StatusDescription = statusDescription; - ftpResponse.StatusCode = statusCode; - asyncRead.SetCompleted (true, ftpResponse); + if (ftpResponse.IsFinal ()) { + State = RequestState.Finished; + } + } + + 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) { + result = local_path; + } else { + if (local_path [0] == '/') + local_path = local_path.Substring (1); + Uri initial = new Uri (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 () { + SendCommand (QuitCommand); + controlStream.Close (); } - public override WebResponse GetResponse () - { - IAsyncResult asyncResult = BeginGetResponse (null, null); - return EndGetResponse (asyncResult); + private void CloseDataConnection () { + if(dataSocket != null) + dataSocket.Close (); } - 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 = ChangeDir; - 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 = new EmptyStream (); + + 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, dataSocket, false); + asyncResult.Stream = requestStream; } - public override Stream GetRequestStream () + void DownloadData () { - IAsyncResult asyncResult = BeginGetRequestStream (null, null); - return EndGetRequestStream (asyncResult); + State = RequestState.OpeningData; + + // Handle content offset + if (offset > 0) { + FtpStatus status = SendCommand (RestCommand, offset.ToString ()); + + if (status.StatusCode != FtpStatusCode.FileCommandPending) + throw CreateExceptionFromResponse (status); + } + + OpenDataConnection (); + + State = RequestState.TransferInProgress; + ftpResponse.Stream = new FtpDataStream (this, dataSocket, 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 () { 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) { + 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", null, + WebExceptionStatus.UnknownError, ftpResponse); controlStream = new NetworkStream (sock); controlReader = new StreamReader (controlStream, Encoding.ASCII); - if (!Authenticate ()) { - SetResponseError (CreateExceptionFromResponse ()); - return false; - } + State = RequestState.Authenticating; - return true; + 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] == '"') + msg = msg.Substring (1, msg.Length - 2); + + 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 +827,71 @@ 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 (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) + void SetCompleteWithError (Exception exc) { - FtpAsyncResult ar = asyncRead; - if (ar == null) - ar = asyncWrite; - - ar.SetCompleted (true, exc); - ar.DoCallback (); + 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 +903,125 @@ 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; - } + if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails && + method != WebRequestMethods.Ftp.UploadFileWithUniqueName) { + status = SendCommand (method, file_name); + } else { + status = SendCommand (method); } - status = SendCommand (method, requestUri.LocalPath); - if (status != FtpStatusCode.OpeningData) { - SetResponseError (CreateExceptionFromResponse ()); - return false; - } - + if (status.StatusCode != FtpStatusCode.OpeningData && status.StatusCode != FtpStatusCode.DataAlreadyOpen) + throw CreateExceptionFromResponse (status); + if (usePassive) { dataSocket = s; - return true; } + 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; - } + dataSocket = incoming; + } - s.Close (); - dataSocket = incoming; - return true; + if (EnableSsl) { + InitiateSecureConnection (ref controlStream); + controlReader = new StreamReader (controlStream, Encoding.ASCII); + } + + 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; - status = SendCommand (UserCommand, username); - if (status == FtpStatusCode.LoggedInProceed) { - ftpResponse.WelcomeMessage = statusDescription; - return true; + if (EnableSsl) { + InitiateSecureConnection (ref controlStream); + controlReader = new StreamReader (controlStream, Encoding.ASCII); } - if (status == FtpStatusCode.SendPasswordCommand) { - status = SendCommand (PasswordCommand, password); - if (status == FtpStatusCode.LoggedInProceed) { - ftpResponse.WelcomeMessage = statusDescription; - return true; - } + + if (status.StatusCode != FtpStatusCode.SendUserCommand) + throw CreateExceptionFromResponse (status); - return false; + status = SendCommand (UserCommand, username); + + 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); } - FtpStatusCode SendCommand (string command, params string [] parameters) + FtpStatus SendCommand (string command, params string [] parameters) { + return SendCommand (true, command, parameters); + } + + FtpStatus SendCommand (bool waitResponse, string command, params string [] parameters) { byte [] cmd; string commandString = command; @@ -837,33 +1034,104 @@ 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 [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 NetworkStream stream) { + FtpStatus status = SendCommand (AuthCommand, "TLS"); + + if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession) { + throw CreateExceptionFromResponse (status); } - if (responseString == null || responseString.Length < 3) - return FtpStatusCode.ServiceNotAvalaible; + ChangeToSSLSocket (ref stream); + } + + internal static bool ChangeToSSLSocket (ref NetworkStream stream) { +#if TARGET_JVM + stream.ChangeToSSLSocket (); + return true; +#else + throw new NotImplementedException (); +#endif + } + + bool InFinalState () { + return (State == RequestState.Aborted || State == RequestState.Error || State == RequestState.Finished); + } + + bool InProgress () { + return (State != RequestState.Before && !InFinalState ()); + } - string codeString = responseString.Substring (0, 3); - int code; - if (!Int32.TryParse (codeString, out code)) - return FtpStatusCode.ServiceNotAvalaible; + internal void CheckIfAborted () { + if (State == RequestState.Aborted) + throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled); + } - statusDescription = responseString; - return statusCode = (FtpStatusCode) code; + void CheckFinalState () { + if (InFinalState ()) + throw new InvalidOperationException ("Cannot change final state"); } + class EmptyStream : MemoryStream + { + internal EmptyStream () + : base (new byte [0], false) { + } + } } }