[sgen] Fix function signature
[mono.git] / mcs / class / System / System.Net / FtpWebRequest.cs
1 //
2 // System.Net.FtpWebRequest.cs
3 //
4 // Authors:
5 //      Carlos Alberto Cortez (calberto.cortez@gmail.com)
6 //
7 // (c) Copyright 2006 Novell, Inc. (http://www.novell.com)
8 //
9
10 #if SECURITY_DEP
11 #if MONO_SECURITY_ALIAS
12 extern alias MonoSecurity;
13 using MSI = MonoSecurity::Mono.Security.Interface;
14 #else
15 using MSI = Mono.Security.Interface;
16 #endif
17 #endif
18
19 using System;
20 using System.Globalization;
21 using System.IO;
22 using System.Net.Sockets;
23 using System.Text;
24 using System.Threading;
25 using System.Net.Cache;
26 using System.Security.Cryptography.X509Certificates;
27 using System.Net;
28 using System.Net.Security;
29 using System.Security.Authentication;
30 using Mono.Net.Security;
31
32 namespace System.Net
33 {
34         public sealed class FtpWebRequest : WebRequest
35         {
36                 Uri requestUri;
37                 string file_name; // By now, used for upload
38                 ServicePoint servicePoint;
39                 Stream origDataStream;
40                 Stream dataStream;
41                 Stream controlStream;
42                 StreamReader controlReader;
43                 NetworkCredential credentials;
44                 IPHostEntry hostEntry;
45                 IPEndPoint localEndPoint;
46                 IPEndPoint remoteEndPoint;
47                 IWebProxy proxy;
48                 int timeout = 100000;
49                 int rwTimeout = 300000;
50                 long offset = 0;
51                 bool binary = true;
52                 bool enableSsl = false;
53                 bool usePassive = true;
54                 bool keepAlive = false;
55                 string method = WebRequestMethods.Ftp.DownloadFile;
56                 string renameTo;
57                 object locker = new object ();
58                 
59                 RequestState requestState = RequestState.Before;
60                 FtpAsyncResult asyncResult;
61                 FtpWebResponse ftpResponse;
62                 Stream requestStream;
63                 string initial_path;
64
65                 const string ChangeDir = "CWD";
66                 const string UserCommand = "USER";
67                 const string PasswordCommand = "PASS";
68                 const string TypeCommand = "TYPE";
69                 const string PassiveCommand = "PASV";
70                 const string ExtendedPassiveCommand = "EPSV";
71                 const string PortCommand = "PORT";
72                 const string ExtendedPortCommand = "EPRT";
73                 const string AbortCommand = "ABOR";
74                 const string AuthCommand = "AUTH";
75                 const string RestCommand = "REST";
76                 const string RenameFromCommand = "RNFR";
77                 const string RenameToCommand = "RNTO";
78                 const string QuitCommand = "QUIT";
79                 const string EOL = "\r\n"; // Special end of line
80
81                 enum RequestState
82                 {
83                         Before,
84                         Scheduled,
85                         Connecting,
86                         Authenticating,
87                         OpeningData,
88                         TransferInProgress,
89                         Finished,
90                         Aborted,
91                         Error
92                 }
93
94                 // sorted commands
95                 static readonly string [] supportedCommands = new string [] {
96                         WebRequestMethods.Ftp.AppendFile, // APPE
97                         WebRequestMethods.Ftp.DeleteFile, // DELE
98                         WebRequestMethods.Ftp.ListDirectoryDetails, // LIST
99                         WebRequestMethods.Ftp.GetDateTimestamp, // MDTM
100                         WebRequestMethods.Ftp.MakeDirectory, // MKD
101                         WebRequestMethods.Ftp.ListDirectory, // NLST
102                         WebRequestMethods.Ftp.PrintWorkingDirectory, // PWD
103                         WebRequestMethods.Ftp.Rename, // RENAME
104                         WebRequestMethods.Ftp.DownloadFile, // RETR
105                         WebRequestMethods.Ftp.RemoveDirectory, // RMD
106                         WebRequestMethods.Ftp.GetFileSize, // SIZE
107                         WebRequestMethods.Ftp.UploadFile, // STOR
108                         WebRequestMethods.Ftp.UploadFileWithUniqueName // STUR
109                         };
110
111                 Encoding dataEncoding = Encoding.UTF8;
112
113                 internal FtpWebRequest (Uri uri) 
114                 {
115                         this.requestUri = uri;
116                         this.proxy = GlobalProxySelection.Select;
117                 }
118
119                 static Exception GetMustImplement ()
120                 {
121                         return new NotImplementedException ();
122                 }
123                 
124                 [MonoTODO]
125                 public X509CertificateCollection ClientCertificates
126                 {
127                         get {
128                                 throw GetMustImplement ();
129                         }
130                         set {
131                                 throw GetMustImplement ();
132                         }
133                 }
134                 
135                 [MonoTODO]
136                 public override string ConnectionGroupName
137                 {
138                         get {
139                                 throw GetMustImplement ();
140                         }
141                         set {
142                                 throw GetMustImplement ();
143                         }
144                 }
145
146                 public override string ContentType {
147                         get {
148                                 throw new NotSupportedException ();
149                         }
150                         set {
151                                 throw new NotSupportedException ();
152                         }
153                 }
154
155                 public override long ContentLength {
156                         get {
157                                 return 0;
158                         } 
159                         set {
160                                 // DO nothing
161                         }
162                 }
163
164                 public long ContentOffset {
165                         get {
166                                 return offset;
167                         }
168                         set {
169                                 CheckRequestStarted ();
170                                 if (value < 0)
171                                         throw new ArgumentOutOfRangeException ();
172
173                                 offset = value;
174                         }
175                 }
176
177                 public override ICredentials Credentials {
178                         get {
179                                 return credentials;
180                         }
181                         set {
182                                 CheckRequestStarted ();
183                                 if (value == null)
184                                         throw new ArgumentNullException ();
185                                 if (!(value is NetworkCredential))
186                                         throw new ArgumentException ();
187
188                                 credentials = value as NetworkCredential;
189                         }
190                 }
191
192 #if !MOBILE
193                 [MonoTODO]
194                 public static new RequestCachePolicy DefaultCachePolicy
195                 {
196                         get {
197                                 throw GetMustImplement ();
198                         }
199                         set {
200                                 throw GetMustImplement ();
201                         }
202                 }
203 #endif
204
205                 public bool EnableSsl {
206                         get {
207                                 return enableSsl;
208                         }
209                         set {
210                                 CheckRequestStarted ();
211                                 enableSsl = value;
212                         }
213                 }
214
215                 [MonoTODO]
216                 public override WebHeaderCollection Headers
217                 {
218                         get {
219                                 throw GetMustImplement ();
220                         }
221                         set {
222                                 throw GetMustImplement ();
223                         }
224                 }
225
226                 [MonoTODO ("We don't support KeepAlive = true")]
227                 public bool KeepAlive {
228                         get {
229                                 return keepAlive;
230                         }
231                         set {
232                                 CheckRequestStarted ();
233                                 //keepAlive = value;
234                         }
235                 }
236
237                 public override string Method {
238                         get {
239                                 return method;
240                         }
241                         set {
242                                 CheckRequestStarted ();
243                                 if (value == null)
244                                         throw new ArgumentNullException ("Method string cannot be null");
245
246                                 if (value.Length == 0 || Array.BinarySearch (supportedCommands, value) < 0)
247                                         throw new ArgumentException ("Method not supported", "value");
248                                 
249                                 method = value;
250                         }
251                 }
252
253                 public override bool PreAuthenticate {
254                         get {
255                                 throw new NotSupportedException ();
256                         }
257                         set {
258                                 throw new NotSupportedException ();
259                         }
260                 }
261
262                 public override IWebProxy Proxy {
263                         get {
264                                 return proxy;
265                         }
266                         set {
267                                 CheckRequestStarted ();
268                                 proxy = value;
269                         }
270                 }
271
272                 public int ReadWriteTimeout {
273                         get {
274                                 return rwTimeout;
275                         }
276                         set {
277                                 CheckRequestStarted ();
278
279                                 if (value < - 1)
280                                         throw new ArgumentOutOfRangeException ();
281                                 else
282                                         rwTimeout = value;
283                         }
284                 }
285
286                 public string RenameTo {
287                         get {
288                                 return renameTo;
289                         }
290                         set {
291                                 CheckRequestStarted ();
292                                 if (value == null || value.Length == 0)
293                                         throw new ArgumentException ("RenameTo value can't be null or empty", "RenameTo");
294
295                                 renameTo = value;
296                         }
297                 }
298
299                 public override Uri RequestUri {
300                         get {
301                                 return requestUri;
302                         }
303                 }
304
305                 public ServicePoint ServicePoint {
306                         get {
307                                 return GetServicePoint ();
308                         }
309                 }
310
311                 public bool UsePassive {
312                         get {
313                                 return usePassive;
314                         }
315                         set {
316                                 CheckRequestStarted ();
317                                 usePassive = value;
318                         }
319                 }
320
321                 [MonoTODO]
322                 public override bool UseDefaultCredentials
323                 {
324                         get {
325                                 throw GetMustImplement ();
326                         }
327                         set {
328                                 throw GetMustImplement ();
329                         }
330                 }
331                 
332                 public bool UseBinary {
333                         get {
334                                 return binary;
335                         } set {
336                                 CheckRequestStarted ();
337                                 binary = value;
338                         }
339                 }
340
341                 public override int Timeout {
342                         get {
343                                 return timeout;
344                         }
345                         set {
346                                 CheckRequestStarted ();
347
348                                 if (value < -1)
349                                         throw new ArgumentOutOfRangeException ();
350                                 else
351                                         timeout = value;
352                         }
353                 }
354
355                 string DataType {
356                         get {
357                                 return binary ? "I" : "A";
358                         }
359                 }
360
361                 RequestState State {
362                         get {
363                                 lock (locker) {
364                                         return requestState;
365                                 }
366                         }
367
368                         set {
369                                 lock (locker) {
370                                         CheckIfAborted ();
371                                         CheckFinalState ();
372                                         requestState = value;
373                                 }
374                         }
375                 }
376
377                 public override void Abort () {
378                         lock (locker) {
379                                 if (State == RequestState.TransferInProgress) {
380                                         /*FtpStatus status = */
381                                         SendCommand (false, AbortCommand);
382                                 }
383
384                                 if (!InFinalState ()) {
385                                         State = RequestState.Aborted;
386                                         ftpResponse = new FtpWebResponse (this, requestUri, method, FtpStatusCode.FileActionAborted, "Aborted by request");
387                                 }
388                         }
389                 }
390
391                 public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state) {
392                         if (asyncResult != null && !asyncResult.IsCompleted) {
393                                 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
394                         }
395
396                         CheckIfAborted ();
397                         
398                         asyncResult = new FtpAsyncResult (callback, state);
399
400                         lock (locker) {
401                                 if (InFinalState ())
402                                         asyncResult.SetCompleted (true, ftpResponse);
403                                 else {
404                                         if (State == RequestState.Before)
405                                                 State = RequestState.Scheduled;
406
407                                         Thread thread = new Thread (ProcessRequest);
408                                         thread.IsBackground = true;
409                                         thread.Start ();
410                                 }
411                         }
412
413                         return asyncResult;
414                 }
415
416                 public override WebResponse EndGetResponse (IAsyncResult asyncResult) {
417                         if (asyncResult == null)
418                                 throw new ArgumentNullException ("AsyncResult cannot be null!");
419
420                         if (!(asyncResult is FtpAsyncResult) || asyncResult != this.asyncResult)
421                                 throw new ArgumentException ("AsyncResult is from another request!");
422
423                         FtpAsyncResult asyncFtpResult = (FtpAsyncResult) asyncResult;
424                         if (!asyncFtpResult.WaitUntilComplete (timeout, false)) {
425                                 Abort ();
426                                 throw new WebException ("Transfer timed out.", WebExceptionStatus.Timeout);
427                         }
428
429                         CheckIfAborted ();
430
431                         asyncResult = null;
432
433                         if (asyncFtpResult.GotException)
434                                 throw asyncFtpResult.Exception;
435
436                         return asyncFtpResult.Response;
437                 }
438
439                 public override WebResponse GetResponse () {
440                         IAsyncResult asyncResult = BeginGetResponse (null, null);
441                         return EndGetResponse (asyncResult);
442                 }
443
444                 public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state) {
445                         if (method != WebRequestMethods.Ftp.UploadFile && method != WebRequestMethods.Ftp.UploadFileWithUniqueName &&
446                                         method != WebRequestMethods.Ftp.AppendFile)
447                                 throw new ProtocolViolationException ();
448
449                         lock (locker) {
450                                 CheckIfAborted ();
451
452                                 if (State != RequestState.Before)
453                                         throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
454
455                                 State = RequestState.Scheduled;
456                         }
457
458                         asyncResult = new FtpAsyncResult (callback, state);
459                         Thread thread = new Thread (ProcessRequest);
460                         thread.IsBackground = true;
461                         thread.Start ();
462
463                         return asyncResult;
464                 }
465
466                 public override Stream EndGetRequestStream (IAsyncResult asyncResult) {
467                         if (asyncResult == null)
468                                 throw new ArgumentNullException ("asyncResult");
469
470                         if (!(asyncResult is FtpAsyncResult))
471                                 throw new ArgumentException ("asyncResult");
472
473                         if (State == RequestState.Aborted) {
474                                 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
475                         }
476                         
477                         if (asyncResult != this.asyncResult)
478                                 throw new ArgumentException ("AsyncResult is from another request!");
479
480                         FtpAsyncResult res = (FtpAsyncResult) asyncResult;
481
482                         if (!res.WaitUntilComplete (timeout, false)) {
483                                 Abort ();
484                                 throw new WebException ("Request timed out");
485                         }
486
487                         if (res.GotException)
488                                 throw res.Exception;
489
490                         return res.Stream;
491                 }
492
493                 public override Stream GetRequestStream () {
494                         IAsyncResult asyncResult = BeginGetRequestStream (null, null);
495                         return EndGetRequestStream (asyncResult);
496                 }
497                 
498                 ServicePoint GetServicePoint ()
499                 {
500                         if (servicePoint == null)
501                                 servicePoint = ServicePointManager.FindServicePoint (requestUri, proxy);
502
503                         return servicePoint;
504                 }
505
506                 // Probably move some code of command connection here
507                 void ResolveHost ()
508                 {
509                         CheckIfAborted ();
510                         hostEntry = GetServicePoint ().HostEntry;
511
512                         if (hostEntry == null) {
513                                 ftpResponse.UpdateStatus (new FtpStatus(FtpStatusCode.ActionAbortedLocalProcessingError, "Cannot resolve server name"));
514                                 throw new WebException ("The remote server name could not be resolved: " + requestUri,
515                                         null, WebExceptionStatus.NameResolutionFailure, ftpResponse);
516                         }
517                 }
518
519                 void ProcessRequest () {
520
521                         if (State == RequestState.Scheduled) {
522                                 ftpResponse = new FtpWebResponse (this, requestUri, method, keepAlive);
523
524                                 try {
525                                         ProcessMethod ();
526                                         //State = RequestState.Finished;
527                                         //finalResponse = ftpResponse;
528                                         asyncResult.SetCompleted (false, ftpResponse);
529                                 }
530                                 catch (Exception e) {
531                                         if (!GetServicePoint ().UsesProxy)
532                                                 State = RequestState.Error;
533                                         SetCompleteWithError (e);
534                                 }
535                         }
536                         else {
537                                 if (InProgress ()) {
538                                         FtpStatus status = GetResponseStatus ();
539
540                                         ftpResponse.UpdateStatus (status);
541
542                                         if (ftpResponse.IsFinal ()) {
543                                                 State = RequestState.Finished;
544                                         }
545                                 }
546
547                                 asyncResult.SetCompleted (false, ftpResponse);
548                         }
549                 }
550
551                 void SetType ()
552                 {
553                         if (binary) {
554                                 FtpStatus status = SendCommand (TypeCommand, DataType);
555                                 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
556                                         throw CreateExceptionFromResponse (status);
557                         }
558                 }
559
560                 string GetRemoteFolderPath (Uri uri)
561                 {
562                         string result;
563                         string local_path = Uri.UnescapeDataString (uri.LocalPath);
564                         if (initial_path == null || initial_path == "/") {
565                                 result = local_path;
566                         } else {
567                                 if (local_path [0] == '/')
568                                         local_path = local_path.Substring (1);
569
570                                 UriBuilder initialBuilder = new UriBuilder () {
571                                         Scheme  = "ftp",
572                                         Host    = "dummy-host",
573                                         Path    = initial_path,
574                                 };
575                                 Uri initial = initialBuilder.Uri;
576                                 result = new Uri (initial, local_path).LocalPath;
577                         }
578
579                         int last = result.LastIndexOf ('/');
580                         if (last == -1)
581                                 return null;
582
583                         return result.Substring (0, last + 1);
584                 }
585
586                 void CWDAndSetFileName (Uri uri)
587                 {
588                         string remote_folder = GetRemoteFolderPath (uri);
589                         FtpStatus status;
590                         if (remote_folder != null) {
591                                 status = SendCommand (ChangeDir, remote_folder);
592                                 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
593                                         throw CreateExceptionFromResponse (status);
594
595                                 int last = uri.LocalPath.LastIndexOf ('/');
596                                 if (last >= 0) {
597                                         file_name = Uri.UnescapeDataString (uri.LocalPath.Substring (last + 1));
598                                 }
599                         }
600                 }
601
602                 void ProcessMethod ()
603                 {
604                         ServicePoint sp = GetServicePoint ();
605                         if (sp.UsesProxy) {
606                                 if (method != WebRequestMethods.Ftp.DownloadFile)
607                                         throw new NotSupportedException ("FTP+proxy only supports RETR");
608
609                                 HttpWebRequest req = (HttpWebRequest) WebRequest.Create (proxy.GetProxy (requestUri));
610                                 req.Address = requestUri;
611                                 requestState = RequestState.Finished;
612                                 WebResponse response = req.GetResponse ();
613                                 ftpResponse.Stream = new FtpDataStream (this, response.GetResponseStream (), true);
614                                 ftpResponse.StatusCode = FtpStatusCode.CommandOK;
615                                 return;
616                         }
617                         State = RequestState.Connecting;
618
619                         ResolveHost ();
620
621                         OpenControlConnection ();
622                         CWDAndSetFileName (requestUri);
623                         SetType ();
624
625                         switch (method) {
626                         // Open data connection and receive data
627                         case WebRequestMethods.Ftp.DownloadFile:
628                         case WebRequestMethods.Ftp.ListDirectory:
629                         case WebRequestMethods.Ftp.ListDirectoryDetails:
630                                 DownloadData ();
631                                 break;
632                         // Open data connection and send data
633                         case WebRequestMethods.Ftp.AppendFile:
634                         case WebRequestMethods.Ftp.UploadFile:
635                         case WebRequestMethods.Ftp.UploadFileWithUniqueName:
636                                 UploadData ();
637                                 break;
638                         // Get info from control connection
639                         case WebRequestMethods.Ftp.GetFileSize:
640                         case WebRequestMethods.Ftp.GetDateTimestamp:
641                         case WebRequestMethods.Ftp.PrintWorkingDirectory:
642                         case WebRequestMethods.Ftp.MakeDirectory:
643                         case WebRequestMethods.Ftp.Rename:
644                         case WebRequestMethods.Ftp.DeleteFile:
645                                 ProcessSimpleMethod ();
646                                 break;
647                         default: // What to do here?
648                                 throw new Exception (String.Format ("Support for command {0} not implemented yet", method));
649                         }
650
651                         CheckIfAborted ();
652                 }
653
654                 private void CloseControlConnection () {
655                         if (controlStream != null) {
656                                 SendCommand (QuitCommand);
657                                 controlStream.Close ();
658                                 controlStream = null;
659                         }
660                 }
661
662                 internal void CloseDataConnection () {
663                         if(origDataStream != null) {
664                                 origDataStream.Close ();
665                                 origDataStream = null;
666                         }
667                 }
668
669                 private void CloseConnection () {
670                         CloseControlConnection ();
671                         CloseDataConnection ();
672                 }
673                 
674                 void ProcessSimpleMethod ()
675                 {
676                         State = RequestState.TransferInProgress;
677                         
678                         FtpStatus status;
679                         
680                         if (method == WebRequestMethods.Ftp.PrintWorkingDirectory)
681                                 method = "PWD";
682
683                         if (method == WebRequestMethods.Ftp.Rename)
684                                 method = RenameFromCommand;
685                         
686                         status = SendCommand (method, file_name);
687
688                         ftpResponse.Stream = Stream.Null;
689                         
690                         string desc = status.StatusDescription;
691
692                         switch (method) {
693                         case WebRequestMethods.Ftp.GetFileSize: {
694                                         if (status.StatusCode != FtpStatusCode.FileStatus)
695                                                 throw CreateExceptionFromResponse (status);
696
697                                         int i, len;
698                                         long size;
699                                         for (i = 4, len = 0; i < desc.Length && Char.IsDigit (desc [i]); i++, len++)
700                                                 ;
701
702                                         if (len == 0)
703                                                 throw new WebException ("Bad format for server response in " + method);
704
705                                         if (!Int64.TryParse (desc.Substring (4, len), out size))
706                                                 throw new WebException ("Bad format for server response in " + method);
707
708                                         ftpResponse.contentLength = size;
709                                 }
710                                 break;
711                         case WebRequestMethods.Ftp.GetDateTimestamp:
712                                 if (status.StatusCode != FtpStatusCode.FileStatus)
713                                         throw CreateExceptionFromResponse (status);
714                                 ftpResponse.LastModified = DateTime.ParseExact (desc.Substring (4), "yyyyMMddHHmmss", null);
715                                 break;
716                         case WebRequestMethods.Ftp.MakeDirectory:
717                                 if (status.StatusCode != FtpStatusCode.PathnameCreated)
718                                         throw CreateExceptionFromResponse (status);
719                                 break;
720                         case ChangeDir:
721                                 method = WebRequestMethods.Ftp.PrintWorkingDirectory;
722
723                                 if (status.StatusCode != FtpStatusCode.FileActionOK)
724                                         throw CreateExceptionFromResponse (status);
725
726                                 status = SendCommand (method);
727
728                                 if (status.StatusCode != FtpStatusCode.PathnameCreated)
729                                         throw CreateExceptionFromResponse (status);
730                                 break;
731                         case RenameFromCommand:
732                                 method = WebRequestMethods.Ftp.Rename;
733                                 if (status.StatusCode != FtpStatusCode.FileCommandPending) 
734                                         throw CreateExceptionFromResponse (status);
735                                 // Pass an empty string if RenameTo wasn't specified
736                                 status = SendCommand (RenameToCommand, renameTo != null ? renameTo : String.Empty);
737                                 if (status.StatusCode != FtpStatusCode.FileActionOK)
738                                         throw CreateExceptionFromResponse (status);
739                                 break;
740                         case WebRequestMethods.Ftp.DeleteFile:
741                                 if (status.StatusCode != FtpStatusCode.FileActionOK)  {
742                                         throw CreateExceptionFromResponse (status);
743                                 }
744                                 break;
745                         }
746
747                         State = RequestState.Finished;
748                 }
749
750                 void UploadData ()
751                 {
752                         State = RequestState.OpeningData;
753
754                         OpenDataConnection ();
755
756                         State = RequestState.TransferInProgress;
757                         requestStream = new FtpDataStream (this, dataStream, false);
758                         asyncResult.Stream = requestStream;
759                 }
760
761                 void DownloadData ()
762                 {
763                         State = RequestState.OpeningData;
764
765                         OpenDataConnection ();
766
767                         State = RequestState.TransferInProgress;
768                         ftpResponse.Stream = new FtpDataStream (this, dataStream, true);
769                 }
770
771                 void CheckRequestStarted ()
772                 {
773                         if (State != RequestState.Before)
774                                 throw new InvalidOperationException ("There is a request currently in progress");
775                 }
776
777                 void OpenControlConnection ()
778                 {
779                         Exception exception = null;
780                         Socket sock = null;
781                         foreach (IPAddress address in hostEntry.AddressList) {
782                                 sock = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
783
784                                 remoteEndPoint = new IPEndPoint (address, requestUri.Port);
785
786                                 if (!ServicePoint.CallEndPointDelegate (sock, remoteEndPoint)) {
787                                         sock.Close ();
788                                         sock = null;
789                                 } else {
790                                         try {
791                                                 sock.Connect (remoteEndPoint);
792                                                 localEndPoint = (IPEndPoint) sock.LocalEndPoint;
793                                                 break;
794                                         } catch (SocketException exc) {
795                                                 exception = exc;
796                                                 sock.Close ();
797                                                 sock = null;
798                                         }
799                                 }
800                         }
801
802                         // Couldn't connect to any address
803                         if (sock == null)
804                                 throw new WebException ("Unable to connect to remote server", exception,
805                                                 WebExceptionStatus.UnknownError, ftpResponse);
806
807                         controlStream = new NetworkStream (sock);
808                         controlReader = new StreamReader (controlStream, Encoding.ASCII);
809
810                         State = RequestState.Authenticating;
811
812                         Authenticate ();
813                         FtpStatus status = SendCommand ("OPTS", "utf8", "on");
814                         if ((int)status.StatusCode < 200 || (int)status.StatusCode > 300)
815                                 dataEncoding = Encoding.Default;
816                         else
817                                 dataEncoding = Encoding.UTF8;
818
819                         status = SendCommand (WebRequestMethods.Ftp.PrintWorkingDirectory);
820                         initial_path = GetInitialPath (status);
821                 }
822
823                 static string GetInitialPath (FtpStatus status)
824                 {
825                         int s = (int) status.StatusCode;
826                         if (s < 200 || s > 300 || status.StatusDescription.Length <= 4)
827                                 throw new WebException ("Error getting current directory: " + status.StatusDescription, null,
828                                                 WebExceptionStatus.UnknownError, null);
829
830                         string msg = status.StatusDescription.Substring (4);
831                         if (msg [0] == '"') {
832                                 int next_quote = msg.IndexOf ('\"', 1);
833                                 if (next_quote == -1)
834                                         throw new WebException ("Error getting current directory: PWD -> " + status.StatusDescription, null,
835                                                                 WebExceptionStatus.UnknownError, null);
836
837                                 msg = msg.Substring (1, next_quote - 1);
838                         }
839
840                         if (!msg.EndsWith ("/"))
841                                 msg += "/";
842                         return msg;
843                 }
844
845                 // Probably we could do better having here a regex
846                 Socket SetupPassiveConnection (string statusDescription, bool ipv6)
847                 {
848                         // Current response string
849                         string response = statusDescription;
850                         if (response.Length < 4)
851                                 throw new WebException ("Cannot open passive data connection");
852
853                         int port = ipv6 ? GetPortV6 (response) : GetPortV4 (response);
854
855                         if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
856                                 throw new WebException ("Cannot open passive data connection");
857
858                         IPEndPoint ep = new IPEndPoint (remoteEndPoint.Address, port);
859                         Socket sock = new Socket (ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
860                         try {
861                                 sock.Connect (ep);
862                         } catch (SocketException) {
863                                 sock.Close ();
864                                 throw new WebException ("Cannot open passive data connection");
865                         }
866
867                         return sock;
868                 }
869
870                 // GetPortV4, GetPortV6, FormatAddress and FormatAddressV6 are copied from referencesource
871                 // TODO: replace FtpWebRequest completely.
872                 private int GetPortV4(string responseString)
873                 {
874                         string [] parsedList = responseString.Split(new char [] {' ', '(', ',', ')'});
875
876                         // We need at least the status code and the port
877                         if (parsedList.Length <= 7) {
878                                 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
879                         }
880
881                         int index = parsedList.Length-1;
882                         // skip the last non-number token (e.g. terminating '.')
883 #if MONO
884                         // the MS code expects \r\n here in parsedList[index],
885                         // but we're stripping the EOL off earlier so the array contains
886                         // an empty string here which would make Char.IsNumber throw
887                         // TODO: this can be removed once we switch FtpWebRequest to referencesource
888                         if (parsedList[index] == "" || !Char.IsNumber(parsedList[index], 0))
889 #else
890                         if (!Char.IsNumber(parsedList[index], 0))
891 #endif
892                                 index--;
893
894                         int port = Convert.ToByte(parsedList[index--], NumberFormatInfo.InvariantInfo);
895                         port = port |
896                                 (Convert.ToByte(parsedList[index--], NumberFormatInfo.InvariantInfo) << 8);
897
898                         return port;
899                 }
900
901                 private int GetPortV6(string responseString)
902                 {
903                         int pos1 = responseString.LastIndexOf("(");
904                         int pos2 = responseString.LastIndexOf(")");
905                         if (pos1 == -1 || pos2 <= pos1) 
906                                 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
907
908                         // addressInfo will contain a string of format "|||<tcp-port>|"
909                         string addressInfo = responseString.Substring(pos1+1, pos2-pos1-1);
910
911                         // Although RFC2428 recommends using "|" as the delimiter,
912                         // It allows ASCII characters in range 33-126 inclusive.
913                         // We should consider allowing the full range.
914
915                         string [] parsedList = addressInfo.Split(new char [] {'|'});
916                         if (parsedList.Length < 4)
917                                 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
918                         
919                         return Convert.ToInt32(parsedList[3], NumberFormatInfo.InvariantInfo);
920                 }
921
922                 private String FormatAddress(IPAddress address, int Port )
923                 {
924                         byte [] localAddressInBytes = address.GetAddressBytes();
925
926                         // produces a string in FTP IPAddress/Port encoding (a1, a2, a3, a4, p1, p2), for sending as a parameter
927                         // to the port command.
928                         StringBuilder sb = new StringBuilder(32);
929                         foreach (byte element in localAddressInBytes) {
930                                 sb.Append(element);
931                                 sb.Append(',');
932                         }
933                         sb.Append(Port / 256 );
934                         sb.Append(',');
935                         sb.Append(Port % 256 );
936                         return sb.ToString();
937                 }
938
939                 private string FormatAddressV6(IPAddress address, int port) {
940                         StringBuilder sb = new StringBuilder(43); // based on max size of IPv6 address + port + seperators
941                         String addressString = address.ToString();
942                         sb.Append("|2|");
943                         sb.Append(addressString);
944                         sb.Append('|');
945                         sb.Append(port.ToString(NumberFormatInfo.InvariantInfo));
946                         sb.Append('|');
947                         return sb.ToString();
948                 }
949                 //
950
951                 Exception CreateExceptionFromResponse (FtpStatus status)
952                 {
953                         FtpWebResponse ftpResponse = new FtpWebResponse (this, requestUri, method, status);
954                         
955                         WebException exc = new WebException ("Server returned an error: " + status.StatusDescription, 
956                                 null, WebExceptionStatus.ProtocolError, ftpResponse);
957                         return exc;
958                 }
959                 
960                 // Here we could also get a server error, so be cautious
961                 internal void SetTransferCompleted ()
962                 {
963                         if (InFinalState ())
964                                 return;
965
966                         State = RequestState.Finished;
967                         FtpStatus status = GetResponseStatus ();
968                         ftpResponse.UpdateStatus (status);
969                         if(!keepAlive)
970                                 CloseConnection ();
971                 }
972
973                 internal void OperationCompleted ()
974                 {
975                         if(!keepAlive)
976                                 CloseConnection ();
977                 }
978
979                 void SetCompleteWithError (Exception exc)
980                 {
981                         if (asyncResult != null) {
982                                 asyncResult.SetCompleted (false, exc);
983                         }
984                 }
985
986                 Socket InitDataConnection ()
987                 {
988                         FtpStatus status;
989                         bool ipv6 = remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6;
990
991                         if (usePassive) {
992                                 status = SendCommand (ipv6 ? ExtendedPassiveCommand : PassiveCommand);
993                                 if (status.StatusCode != (ipv6 ? (FtpStatusCode)229 : FtpStatusCode.EnteringPassive)) { // FtpStatusCode doesn't contain code 229 for EPSV so we need to cast...
994                                         throw CreateExceptionFromResponse (status);
995                                 }
996                                 
997                                 return SetupPassiveConnection (status.StatusDescription, ipv6);
998                         }
999
1000                         // Open a socket to listen the server's connection
1001                         Socket sock = new Socket (remoteEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
1002                         try {
1003                                 sock.Bind (new IPEndPoint (localEndPoint.Address, 0));
1004                                 sock.Listen (1); // We only expect a connection from server
1005
1006                         } catch (SocketException e) {
1007                                 sock.Close ();
1008
1009                                 throw new WebException ("Couldn't open listening socket on client", e);
1010                         }
1011
1012                         IPEndPoint ep = (IPEndPoint) sock.LocalEndPoint;
1013
1014                         var portParam = ipv6 ? FormatAddressV6 (ep.Address, ep.Port) : FormatAddress (ep.Address, ep.Port);
1015
1016                         status = SendCommand (ipv6 ? ExtendedPortCommand : PortCommand, portParam);
1017                         
1018                         if (status.StatusCode != FtpStatusCode.CommandOK) {
1019                                 sock.Close ();
1020                                 throw (CreateExceptionFromResponse (status));
1021                         }
1022
1023                         return sock;
1024                 }
1025
1026                 void OpenDataConnection ()
1027                 {
1028                         FtpStatus status;
1029                         
1030                         Socket s = InitDataConnection ();
1031
1032                         // Handle content offset
1033                         if (offset > 0) {
1034                                 status = SendCommand (RestCommand, offset.ToString ());
1035                                 if (status.StatusCode != FtpStatusCode.FileCommandPending)
1036                                         throw CreateExceptionFromResponse (status);
1037                         }
1038
1039                         if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails &&
1040                             method != WebRequestMethods.Ftp.UploadFileWithUniqueName) {
1041                                 status = SendCommand (method, file_name);
1042                         } else {
1043                                 status = SendCommand (method);
1044                         }
1045
1046                         if (status.StatusCode != FtpStatusCode.OpeningData && status.StatusCode != FtpStatusCode.DataAlreadyOpen)
1047                                 throw CreateExceptionFromResponse (status);
1048
1049                         if (usePassive) {
1050                                 origDataStream = new NetworkStream (s, true);
1051                                 dataStream = origDataStream;
1052                                 if (EnableSsl)
1053                                         ChangeToSSLSocket (ref dataStream);
1054                         }
1055                         else {
1056
1057                                 // Active connection (use Socket.Blocking to true)
1058                                 Socket incoming = null;
1059                                 try {
1060                                         incoming = s.Accept ();
1061                                 }
1062                                 catch (SocketException) {
1063                                         s.Close ();
1064                                         if (incoming != null)
1065                                                 incoming.Close ();
1066
1067                                         throw new ProtocolViolationException ("Server commited a protocol violation.");
1068                                 }
1069
1070                                 s.Close ();
1071                                 origDataStream = new NetworkStream (incoming, true);
1072                                 dataStream = origDataStream;
1073                                 if (EnableSsl)
1074                                         ChangeToSSLSocket (ref dataStream);
1075                         }
1076
1077                         ftpResponse.UpdateStatus (status);
1078                 }
1079
1080                 void Authenticate ()
1081                 {
1082                         string username = null;
1083                         string password = null;
1084                         string domain = null;
1085
1086                         if (credentials != null) {
1087                                 username = credentials.UserName;
1088                                 password = credentials.Password;
1089                                 domain = credentials.Domain;
1090                         }
1091
1092                         if (username == null)
1093                                 username = "anonymous";
1094                         if (password == null)
1095                                 password = "@anonymous";
1096                         if (!string.IsNullOrEmpty (domain))
1097                                 username = domain + '\\' + username;
1098
1099                         // Connect to server and get banner message
1100                         FtpStatus status = GetResponseStatus ();
1101                         ftpResponse.BannerMessage = status.StatusDescription;
1102
1103                         if (EnableSsl) {
1104                                 InitiateSecureConnection (ref controlStream);
1105                                 controlReader = new StreamReader (controlStream, Encoding.ASCII);
1106                                 status = SendCommand ("PBSZ", "0");
1107                                 int st = (int) status.StatusCode;
1108                                 if (st < 200 || st >= 300)
1109                                         throw CreateExceptionFromResponse (status);
1110                                 // TODO: what if "PROT P" is denied by the server? What does MS do?
1111                                 status = SendCommand ("PROT", "P");
1112                                 st = (int) status.StatusCode;
1113                                 if (st < 200 || st >= 300)
1114                                         throw CreateExceptionFromResponse (status);
1115
1116                                 status = new FtpStatus (FtpStatusCode.SendUserCommand, "");
1117                         }
1118                         
1119                         if (status.StatusCode != FtpStatusCode.SendUserCommand)
1120                                 throw CreateExceptionFromResponse (status);
1121
1122                         status = SendCommand (UserCommand, username);
1123
1124                         switch (status.StatusCode) {
1125                         case FtpStatusCode.SendPasswordCommand:
1126                                 status = SendCommand (PasswordCommand, password);
1127                                 if (status.StatusCode != FtpStatusCode.LoggedInProceed)
1128                                         throw CreateExceptionFromResponse (status);
1129                                 break;
1130                         case FtpStatusCode.LoggedInProceed:
1131                                 break;
1132                         default:
1133                                 throw CreateExceptionFromResponse (status);
1134                         }
1135
1136                         ftpResponse.WelcomeMessage = status.StatusDescription;
1137                         ftpResponse.UpdateStatus (status);
1138                 }
1139
1140                 FtpStatus SendCommand (string command, params string [] parameters) {
1141                         return SendCommand (true, command, parameters);
1142                 }
1143
1144                 FtpStatus SendCommand (bool waitResponse, string command, params string [] parameters)
1145                 {
1146                         byte [] cmd;
1147                         string commandString = command;
1148                         if (parameters.Length > 0)
1149                                 commandString += " " + String.Join (" ", parameters);
1150
1151                         commandString += EOL;
1152                         cmd = dataEncoding.GetBytes (commandString);
1153                         try {
1154                                 controlStream.Write (cmd, 0, cmd.Length);
1155                         } catch (IOException) {
1156                                 //controlStream.Close ();
1157                                 return new FtpStatus(FtpStatusCode.ServiceNotAvailable, "Write failed");
1158                         }
1159
1160                         if(!waitResponse)
1161                                 return null;
1162                         
1163                         FtpStatus result = GetResponseStatus ();
1164                         if (ftpResponse != null)
1165                                 ftpResponse.UpdateStatus (result);
1166                         return result;
1167                 }
1168
1169                 internal static FtpStatus ServiceNotAvailable ()
1170                 {
1171                         return new FtpStatus (FtpStatusCode.ServiceNotAvailable, Locale.GetText ("Invalid response from server"));
1172                 }
1173                 
1174                 internal FtpStatus GetResponseStatus ()
1175                 {
1176                         while (true) {
1177                                 string response = null;
1178
1179                                 try {
1180                                         response = controlReader.ReadLine ();
1181                                 } catch (IOException) {
1182                                 }
1183
1184                                 if (response == null || response.Length < 3)
1185                                         return ServiceNotAvailable ();
1186
1187                                 int code;
1188                                 if (!Int32.TryParse (response.Substring (0, 3), out code))
1189                                         return ServiceNotAvailable ();
1190
1191                                 if (response.Length > 3 && response [3] == '-'){
1192                                         string line = null;
1193                                         string find = code.ToString() + ' ';
1194                                         while (true){
1195                                                 line = null;
1196                                                 try {
1197                                                         line = controlReader.ReadLine();
1198                                                 } catch (IOException) {
1199                                                 }
1200                                                 if (line == null)
1201                                                         return ServiceNotAvailable ();
1202                                                 
1203                                                 response += Environment.NewLine + line;
1204
1205                                                 if (line.StartsWith(find, StringComparison.Ordinal))
1206                                                         break;
1207                                         } 
1208                                 }
1209                                 return new FtpStatus ((FtpStatusCode) code, response);
1210                         }
1211                 }
1212
1213                 private void InitiateSecureConnection (ref Stream stream) {
1214                         FtpStatus status = SendCommand (AuthCommand, "TLS");
1215                         if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession)
1216                                 throw CreateExceptionFromResponse (status);
1217
1218                         ChangeToSSLSocket (ref stream);
1219                 }
1220
1221                 internal bool ChangeToSSLSocket (ref Stream stream) {
1222 #if SECURITY_DEP
1223                         var provider = MonoTlsProviderFactory.GetProviderInternal ();
1224                         var settings = MSI.MonoTlsSettings.CopyDefaultSettings ();
1225                         settings.UseServicePointManagerCallback = true;
1226                         var sslStream = provider.CreateSslStream (stream, true, settings);
1227                         sslStream.AuthenticateAsClient (requestUri.Host, null, SslProtocols.Default, false);
1228                         stream = sslStream.AuthenticatedStream;
1229                         return true;
1230 #else
1231                         throw new NotImplementedException ();
1232 #endif
1233                 }
1234                 
1235                 bool InFinalState () {
1236                         return (State == RequestState.Aborted || State == RequestState.Error || State == RequestState.Finished);
1237                 }
1238
1239                 bool InProgress () {
1240                         return (State != RequestState.Before && !InFinalState ());
1241                 }
1242
1243                 internal void CheckIfAborted () {
1244                         if (State == RequestState.Aborted)
1245                                 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
1246                 }
1247
1248                 void CheckFinalState () {
1249                         if (InFinalState ())
1250                                 throw new InvalidOperationException ("Cannot change final state");
1251                 }
1252         }
1253 }
1254
1255