Merge pull request #2171 from lambdageek/dev/fix-marshal
[mono.git] / mcs / class / System / System.Net / FtpWebRequest.cs
index e25259fc14f2f89b85d00007a15701be9b7c80c8..bb900979a48ff614732e30717f06f15925b1981f 100644 (file)
@@ -6,7 +6,15 @@
 //
 // (c) Copyright 2006 Novell, Inc. (http://www.novell.com)
 //
-#if NET_2_0
+
+#if SECURITY_DEP
+#if MONO_SECURITY_ALIAS
+extern alias MonoSecurity;
+using MSI = MonoSecurity::Mono.Security.Interface;
+#else
+using MSI = Mono.Security.Interface;
+#endif
+#endif
 
 using System;
 using System.IO;
@@ -16,7 +24,9 @@ using System.Threading;
 using System.Net.Cache;
 using System.Security.Cryptography.X509Certificates;
 using System.Net;
-
+using System.Net.Security;
+using System.Security.Authentication;
+using Mono.Net.Security;
 
 namespace System.Net
 {
@@ -25,8 +35,9 @@ namespace System.Net
                Uri requestUri;
                string file_name; // By now, used for upload
                ServicePoint servicePoint;
-               Socket dataSocket;
-               NetworkStream controlStream;
+               Stream origDataStream;
+               Stream dataStream;
+               Stream controlStream;
                StreamReader controlReader;
                NetworkCredential credentials;
                IPHostEntry hostEntry;
@@ -93,6 +104,8 @@ namespace System.Net
                        WebRequestMethods.Ftp.UploadFileWithUniqueName // STUR
                        };
 
+               Encoding dataEncoding = Encoding.UTF8;
+
                internal FtpWebRequest (Uri uri) 
                {
                        this.requestUri = uri;
@@ -172,6 +185,7 @@ namespace System.Net
                        }
                }
 
+#if !NET_2_1
                [MonoTODO]
                public static new RequestCachePolicy DefaultCachePolicy
                {
@@ -182,6 +196,7 @@ namespace System.Net
                                throw GetMustImplement ();
                        }
                }
+#endif
 
                public bool EnableSsl {
                        get {
@@ -204,13 +219,14 @@ namespace System.Net
                        }
                }
 
+               [MonoTODO ("We don't support KeepAlive = true")]
                public bool KeepAlive {
                        get {
                                return keepAlive;
                        }
                        set {
                                CheckRequestStarted ();
-                               keepAlive = value;
+                               //keepAlive = value;
                        }
                }
 
@@ -245,9 +261,6 @@ namespace System.Net
                        }
                        set {
                                CheckRequestStarted ();
-                               if (value == null)
-                                       throw new ArgumentNullException ();
-
                                proxy = value;
                        }
                }
@@ -366,7 +379,7 @@ namespace System.Net
 
                                if (!InFinalState ()) {
                                        State = RequestState.Aborted;
-                                       ftpResponse = new FtpWebResponse (requestUri, method, FtpStatusCode.FileActionAborted, "Aborted by request");
+                                       ftpResponse = new FtpWebResponse (this, requestUri, method, FtpStatusCode.FileActionAborted, "Aborted by request");
                                }
                        }
                }
@@ -500,7 +513,7 @@ namespace System.Net
                void ProcessRequest () {
 
                        if (State == RequestState.Scheduled) {
-                               ftpResponse = new FtpWebResponse (requestUri, method, keepAlive);
+                               ftpResponse = new FtpWebResponse (this, requestUri, method, keepAlive);
 
                                try {
                                        ProcessMethod ();
@@ -509,7 +522,8 @@ namespace System.Net
                                        asyncResult.SetCompleted (false, ftpResponse);
                                }
                                catch (Exception e) {
-                                       State = RequestState.Error;
+                                       if (!GetServicePoint ().UsesProxy)
+                                               State = RequestState.Error;
                                        SetCompleteWithError (e);
                                }
                        }
@@ -547,7 +561,12 @@ namespace System.Net
                                if (local_path [0] == '/')
                                        local_path = local_path.Substring (1);
 
-                               Uri initial = new Uri ("ftp://dummy-host" + initial_path);
+                               UriBuilder initialBuilder = new UriBuilder () {
+                                       Scheme  = "ftp",
+                                       Host    = "dummy-host",
+                                       Path    = initial_path,
+                               };
+                               Uri initial = initialBuilder.Uri;
                                result = new Uri (initial, local_path).LocalPath;
                        }
 
@@ -576,6 +595,19 @@ namespace System.Net
 
                void ProcessMethod ()
                {
+                       ServicePoint sp = GetServicePoint ();
+                       if (sp.UsesProxy) {
+                               if (method != WebRequestMethods.Ftp.DownloadFile)
+                                       throw new NotSupportedException ("FTP+proxy only supports RETR");
+
+                               HttpWebRequest req = (HttpWebRequest) WebRequest.Create (proxy.GetProxy (requestUri));
+                               req.Address = requestUri;
+                               requestState = RequestState.Finished;
+                               WebResponse response = req.GetResponse ();
+                               ftpResponse.Stream = new FtpDataStream (this, response.GetResponseStream (), true);
+                               ftpResponse.StatusCode = FtpStatusCode.CommandOK;
+                               return;
+                       }
                        State = RequestState.Connecting;
 
                        ResolveHost ();
@@ -614,13 +646,18 @@ namespace System.Net
                }
 
                private void CloseControlConnection () {
-                       SendCommand (QuitCommand);
-                       controlStream.Close ();
+                       if (controlStream != null) {
+                               SendCommand (QuitCommand);
+                               controlStream.Close ();
+                               controlStream = null;
+                       }
                }
 
-               private void CloseDataConnection () {
-                       if(dataSocket != null)
-                               dataSocket.Close ();
+               internal void CloseDataConnection () {
+                       if(origDataStream != null) {
+                               origDataStream.Close ();
+                               origDataStream = null;
+                       }
                }
 
                private void CloseConnection () {
@@ -635,14 +672,14 @@ namespace System.Net
                        FtpStatus status;
                        
                        if (method == WebRequestMethods.Ftp.PrintWorkingDirectory)
-                               method = ChangeDir;
+                               method = "PWD";
 
                        if (method == WebRequestMethods.Ftp.Rename)
                                method = RenameFromCommand;
                        
                        status = SendCommand (method, file_name);
 
-                       ftpResponse.Stream = new EmptyStream ();
+                       ftpResponse.Stream = Stream.Null;
                        
                        string desc = status.StatusDescription;
 
@@ -711,7 +748,7 @@ namespace System.Net
                        OpenDataConnection ();
 
                        State = RequestState.TransferInProgress;
-                       requestStream = new FtpDataStream (this, dataSocket, false);
+                       requestStream = new FtpDataStream (this, dataStream, false);
                        asyncResult.Stream = requestStream;
                }
 
@@ -719,18 +756,10 @@ namespace System.Net
                {
                        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);
+                       ftpResponse.Stream = new FtpDataStream (this, dataStream, true);
                }
 
                void CheckRequestStarted ()
@@ -776,7 +805,11 @@ namespace System.Net
 
                        Authenticate ();
                        FtpStatus status = SendCommand ("OPTS", "utf8", "on");
-                       // ignore status for OPTS
+                       if ((int)status.StatusCode < 200 || (int)status.StatusCode > 300)
+                               dataEncoding = Encoding.Default;
+                       else
+                               dataEncoding = Encoding.UTF8;
+
                        status = SendCommand (WebRequestMethods.Ftp.PrintWorkingDirectory);
                        initial_path = GetInitialPath (status);
                }
@@ -863,7 +896,7 @@ namespace System.Net
 
                Exception CreateExceptionFromResponse (FtpStatus status)
                {
-                       FtpWebResponse ftpResponse = new FtpWebResponse (requestUri, method, status);
+                       FtpWebResponse ftpResponse = new FtpWebResponse (this, requestUri, method, status);
                        
                        WebException exc = new WebException ("Server returned an error: " + status.StatusDescription, 
                                null, WebExceptionStatus.ProtocolError, ftpResponse);
@@ -883,6 +916,12 @@ namespace System.Net
                                CloseConnection ();
                }
 
+               internal void OperationCompleted ()
+               {
+                       if(!keepAlive)
+                               CloseConnection ();
+               }
+
                void SetCompleteWithError (Exception exc)
                {
                        if (asyncResult != null) {
@@ -937,6 +976,13 @@ namespace System.Net
                        
                        Socket s = InitDataConnection ();
 
+                       // Handle content offset
+                       if (offset > 0) {
+                               status = SendCommand (RestCommand, offset.ToString ());
+                               if (status.StatusCode != FtpStatusCode.FileCommandPending)
+                                       throw CreateExceptionFromResponse (status);
+                       }
+
                        if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails &&
                            method != WebRequestMethods.Ftp.UploadFileWithUniqueName) {
                                status = SendCommand (method, file_name);
@@ -948,7 +994,10 @@ namespace System.Net
                                throw CreateExceptionFromResponse (status);
 
                        if (usePassive) {
-                               dataSocket = s;
+                               origDataStream = new NetworkStream (s, true);
+                               dataStream = origDataStream;
+                               if (EnableSsl)
+                                       ChangeToSSLSocket (ref dataStream);
                        }
                        else {
 
@@ -966,12 +1015,10 @@ namespace System.Net
                                }
 
                                s.Close ();
-                               dataSocket = incoming;
-                       }
-
-                       if (EnableSsl) {
-                               InitiateSecureConnection (ref controlStream);
-                               controlReader = new StreamReader (controlStream, Encoding.ASCII);
+                               origDataStream = new NetworkStream (incoming, true);
+                               dataStream = origDataStream;
+                               if (EnableSsl)
+                                       ChangeToSSLSocket (ref dataStream);
                        }
 
                        ftpResponse.UpdateStatus (status);
@@ -1003,6 +1050,17 @@ namespace System.Net
                        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)
@@ -1038,7 +1096,7 @@ namespace System.Net
                                commandString += " " + String.Join (" ", parameters);
 
                        commandString += EOL;
-                       cmd = Encoding.ASCII.GetBytes (commandString);
+                       cmd = dataEncoding.GetBytes (commandString);
                        try {
                                controlStream.Write (cmd, 0, cmd.Length);
                        } catch (IOException) {
@@ -1077,10 +1135,11 @@ namespace System.Net
                                if (!Int32.TryParse (response.Substring (0, 3), out code))
                                        return ServiceNotAvailable ();
 
-                               if (response [3] == '-'){
+                               if (response.Length > 3 && response [3] == '-'){
                                        string line = null;
                                        string find = code.ToString() + ' ';
                                        while (true){
+                                               line = null;
                                                try {
                                                        line = controlReader.ReadLine();
                                                } catch (IOException) {
@@ -1098,19 +1157,22 @@ namespace System.Net
                        }
                }
 
-               private void InitiateSecureConnection (ref NetworkStream stream) {
+               private void InitiateSecureConnection (ref Stream stream) {
                        FtpStatus status = SendCommand (AuthCommand, "TLS");
-
-                       if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession) {
+                       if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession)
                                throw CreateExceptionFromResponse (status);
-                       }
 
                        ChangeToSSLSocket (ref stream);
                }
 
-               internal static bool ChangeToSSLSocket (ref NetworkStream stream) {
-#if TARGET_JVM
-                       stream.ChangeToSSLSocket ();
+               internal bool ChangeToSSLSocket (ref Stream stream) {
+#if SECURITY_DEP
+                       var provider = MonoTlsProviderFactory.GetProviderInternal ();
+                       var settings = new MSI.MonoTlsSettings ();
+                       settings.UseServicePointManagerCallback = true;
+                       var sslStream = provider.CreateSslStream (stream, true, settings);
+                       sslStream.AuthenticateAsClient (requestUri.Host, null, SslProtocols.Default, false);
+                       stream = sslStream.AuthenticatedStream;
                        return true;
 #else
                        throw new NotImplementedException ();
@@ -1134,15 +1196,7 @@ namespace System.Net
                        if (InFinalState ())
                                throw new InvalidOperationException ("Cannot change final state");
                }
-
-               class EmptyStream : MemoryStream
-               {
-                       internal EmptyStream ()
-                               : base (new byte [0], false) {
-                       }
-               }
        }
 }
 
-#endif