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