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