2 // System.Net.FtpWebRequest.cs
5 // Carlos Alberto Cortez (calberto.cortez@gmail.com)
7 // (c) Copyright 2006 Novell, Inc. (http://www.novell.com)
11 #if MONO_SECURITY_ALIAS
12 extern alias MonoSecurity
;
13 using MSI
= MonoSecurity
::Mono
.Security
.Interface
;
15 using MSI
= Mono
.Security
.Interface
;
21 using System
.Net
.Sockets
;
23 using System
.Threading
;
24 using System
.Net
.Cache
;
25 using System
.Security
.Cryptography
.X509Certificates
;
27 using System
.Net
.Security
;
28 using System
.Security
.Authentication
;
29 using Mono
.Net
.Security
;
33 public sealed class FtpWebRequest
: WebRequest
36 string file_name
; // By now, used for upload
37 ServicePoint servicePoint
;
38 Stream origDataStream
;
41 StreamReader controlReader
;
42 NetworkCredential credentials
;
43 IPHostEntry hostEntry
;
44 IPEndPoint localEndPoint
;
47 int rwTimeout
= 300000;
50 bool enableSsl
= false;
51 bool usePassive
= true;
52 bool keepAlive
= false;
53 string method
= WebRequestMethods
.Ftp
.DownloadFile
;
55 object locker
= new object ();
57 RequestState requestState
= RequestState
.Before
;
58 FtpAsyncResult asyncResult
;
59 FtpWebResponse ftpResponse
;
63 const string ChangeDir
= "CWD";
64 const string UserCommand
= "USER";
65 const string PasswordCommand
= "PASS";
66 const string TypeCommand
= "TYPE";
67 const string PassiveCommand
= "PASV";
68 const string PortCommand
= "PORT";
69 const string AbortCommand
= "ABOR";
70 const string AuthCommand
= "AUTH";
71 const string RestCommand
= "REST";
72 const string RenameFromCommand
= "RNFR";
73 const string RenameToCommand
= "RNTO";
74 const string QuitCommand
= "QUIT";
75 const string EOL
= "\r\n"; // Special end of line
91 static readonly string [] supportedCommands
= new string [] {
92 WebRequestMethods
.Ftp
.AppendFile
, // APPE
93 WebRequestMethods
.Ftp
.DeleteFile
, // DELE
94 WebRequestMethods
.Ftp
.ListDirectoryDetails
, // LIST
95 WebRequestMethods
.Ftp
.GetDateTimestamp
, // MDTM
96 WebRequestMethods
.Ftp
.MakeDirectory
, // MKD
97 WebRequestMethods
.Ftp
.ListDirectory
, // NLST
98 WebRequestMethods
.Ftp
.PrintWorkingDirectory
, // PWD
99 WebRequestMethods
.Ftp
.Rename
, // RENAME
100 WebRequestMethods
.Ftp
.DownloadFile
, // RETR
101 WebRequestMethods
.Ftp
.RemoveDirectory
, // RMD
102 WebRequestMethods
.Ftp
.GetFileSize
, // SIZE
103 WebRequestMethods
.Ftp
.UploadFile
, // STOR
104 WebRequestMethods
.Ftp
.UploadFileWithUniqueName
// STUR
107 Encoding dataEncoding
= Encoding
.UTF8
;
109 internal FtpWebRequest (Uri uri
)
111 this.requestUri
= uri
;
112 this.proxy
= GlobalProxySelection
.Select
;
115 static Exception
GetMustImplement ()
117 return new NotImplementedException ();
121 public X509CertificateCollection ClientCertificates
124 throw GetMustImplement ();
127 throw GetMustImplement ();
132 public override string ConnectionGroupName
135 throw GetMustImplement ();
138 throw GetMustImplement ();
142 public override string ContentType
{
144 throw new NotSupportedException ();
147 throw new NotSupportedException ();
151 public override long ContentLength
{
160 public long ContentOffset
{
165 CheckRequestStarted ();
167 throw new ArgumentOutOfRangeException ();
173 public override ICredentials Credentials
{
178 CheckRequestStarted ();
180 throw new ArgumentNullException ();
181 if (!(value is NetworkCredential
))
182 throw new ArgumentException ();
184 credentials
= value as NetworkCredential
;
190 public static new RequestCachePolicy DefaultCachePolicy
193 throw GetMustImplement ();
196 throw GetMustImplement ();
201 public bool EnableSsl
{
206 CheckRequestStarted ();
212 public override WebHeaderCollection Headers
215 throw GetMustImplement ();
218 throw GetMustImplement ();
222 [MonoTODO ("We don't support KeepAlive = true")]
223 public bool KeepAlive
{
228 CheckRequestStarted ();
233 public override string Method
{
238 CheckRequestStarted ();
240 throw new ArgumentNullException ("Method string cannot be null");
242 if (value.Length
== 0 || Array
.BinarySearch (supportedCommands
, value) < 0)
243 throw new ArgumentException ("Method not supported", "value");
249 public override bool PreAuthenticate
{
251 throw new NotSupportedException ();
254 throw new NotSupportedException ();
258 public override IWebProxy Proxy
{
263 CheckRequestStarted ();
268 public int ReadWriteTimeout
{
273 CheckRequestStarted ();
276 throw new ArgumentOutOfRangeException ();
282 public string RenameTo
{
287 CheckRequestStarted ();
288 if (value == null || value.Length
== 0)
289 throw new ArgumentException ("RenameTo value can't be null or empty", "RenameTo");
295 public override Uri RequestUri
{
301 public ServicePoint ServicePoint
{
303 return GetServicePoint ();
307 public bool UsePassive
{
312 CheckRequestStarted ();
318 public override bool UseDefaultCredentials
321 throw GetMustImplement ();
324 throw GetMustImplement ();
328 public bool UseBinary
{
332 CheckRequestStarted ();
337 public override int Timeout
{
342 CheckRequestStarted ();
345 throw new ArgumentOutOfRangeException ();
353 return binary
? "I" : "A";
368 requestState
= value;
373 public override void Abort () {
375 if (State
== RequestState
.TransferInProgress
) {
376 /*FtpStatus status = */
377 SendCommand (false, AbortCommand
);
380 if (!InFinalState ()) {
381 State
= RequestState
.Aborted
;
382 ftpResponse
= new FtpWebResponse (this, requestUri
, method
, FtpStatusCode
.FileActionAborted
, "Aborted by request");
387 public override IAsyncResult
BeginGetResponse (AsyncCallback callback
, object state
) {
388 if (asyncResult
!= null && !asyncResult
.IsCompleted
) {
389 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
394 asyncResult
= new FtpAsyncResult (callback
, state
);
398 asyncResult
.SetCompleted (true, ftpResponse
);
400 if (State
== RequestState
.Before
)
401 State
= RequestState
.Scheduled
;
403 Thread thread
= new Thread (ProcessRequest
);
404 thread
.IsBackground
= true;
412 public override WebResponse
EndGetResponse (IAsyncResult asyncResult
) {
413 if (asyncResult
== null)
414 throw new ArgumentNullException ("AsyncResult cannot be null!");
416 if (!(asyncResult
is FtpAsyncResult
) || asyncResult
!= this.asyncResult
)
417 throw new ArgumentException ("AsyncResult is from another request!");
419 FtpAsyncResult asyncFtpResult
= (FtpAsyncResult
) asyncResult
;
420 if (!asyncFtpResult
.WaitUntilComplete (timeout
, false)) {
422 throw new WebException ("Transfer timed out.", WebExceptionStatus
.Timeout
);
429 if (asyncFtpResult
.GotException
)
430 throw asyncFtpResult
.Exception
;
432 return asyncFtpResult
.Response
;
435 public override WebResponse
GetResponse () {
436 IAsyncResult asyncResult
= BeginGetResponse (null, null);
437 return EndGetResponse (asyncResult
);
440 public override IAsyncResult
BeginGetRequestStream (AsyncCallback callback
, object state
) {
441 if (method
!= WebRequestMethods
.Ftp
.UploadFile
&& method
!= WebRequestMethods
.Ftp
.UploadFileWithUniqueName
&&
442 method
!= WebRequestMethods
.Ftp
.AppendFile
)
443 throw new ProtocolViolationException ();
448 if (State
!= RequestState
.Before
)
449 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
451 State
= RequestState
.Scheduled
;
454 asyncResult
= new FtpAsyncResult (callback
, state
);
455 Thread thread
= new Thread (ProcessRequest
);
456 thread
.IsBackground
= true;
462 public override Stream
EndGetRequestStream (IAsyncResult asyncResult
) {
463 if (asyncResult
== null)
464 throw new ArgumentNullException ("asyncResult");
466 if (!(asyncResult
is FtpAsyncResult
))
467 throw new ArgumentException ("asyncResult");
469 if (State
== RequestState
.Aborted
) {
470 throw new WebException ("Request aborted", WebExceptionStatus
.RequestCanceled
);
473 if (asyncResult
!= this.asyncResult
)
474 throw new ArgumentException ("AsyncResult is from another request!");
476 FtpAsyncResult res
= (FtpAsyncResult
) asyncResult
;
478 if (!res
.WaitUntilComplete (timeout
, false)) {
480 throw new WebException ("Request timed out");
483 if (res
.GotException
)
489 public override Stream
GetRequestStream () {
490 IAsyncResult asyncResult
= BeginGetRequestStream (null, null);
491 return EndGetRequestStream (asyncResult
);
494 ServicePoint
GetServicePoint ()
496 if (servicePoint
== null)
497 servicePoint
= ServicePointManager
.FindServicePoint (requestUri
, proxy
);
502 // Probably move some code of command connection here
506 hostEntry
= GetServicePoint ().HostEntry
;
508 if (hostEntry
== null) {
509 ftpResponse
.UpdateStatus (new FtpStatus(FtpStatusCode
.ActionAbortedLocalProcessingError
, "Cannot resolve server name"));
510 throw new WebException ("The remote server name could not be resolved: " + requestUri
,
511 null, WebExceptionStatus
.NameResolutionFailure
, ftpResponse
);
515 void ProcessRequest () {
517 if (State
== RequestState
.Scheduled
) {
518 ftpResponse
= new FtpWebResponse (this, requestUri
, method
, keepAlive
);
522 //State = RequestState.Finished;
523 //finalResponse = ftpResponse;
524 asyncResult
.SetCompleted (false, ftpResponse
);
526 catch (Exception e
) {
527 if (!GetServicePoint ().UsesProxy
)
528 State
= RequestState
.Error
;
529 SetCompleteWithError (e
);
534 FtpStatus status
= GetResponseStatus ();
536 ftpResponse
.UpdateStatus (status
);
538 if (ftpResponse
.IsFinal ()) {
539 State
= RequestState
.Finished
;
543 asyncResult
.SetCompleted (false, ftpResponse
);
550 FtpStatus status
= SendCommand (TypeCommand
, DataType
);
551 if ((int) status
.StatusCode
< 200 || (int) status
.StatusCode
>= 300)
552 throw CreateExceptionFromResponse (status
);
556 string GetRemoteFolderPath (Uri uri
)
559 string local_path
= Uri
.UnescapeDataString (uri
.LocalPath
);
560 if (initial_path
== null || initial_path
== "/") {
563 if (local_path
[0] == '/')
564 local_path
= local_path
.Substring (1);
566 UriBuilder initialBuilder
= new UriBuilder () {
571 Uri initial
= initialBuilder
.Uri
;
572 result
= new Uri (initial
, local_path
).LocalPath
;
575 int last
= result
.LastIndexOf ('/');
579 return result
.Substring (0, last
+ 1);
582 void CWDAndSetFileName (Uri uri
)
584 string remote_folder
= GetRemoteFolderPath (uri
);
586 if (remote_folder
!= null) {
587 status
= SendCommand (ChangeDir
, remote_folder
);
588 if ((int) status
.StatusCode
< 200 || (int) status
.StatusCode
>= 300)
589 throw CreateExceptionFromResponse (status
);
591 int last
= uri
.LocalPath
.LastIndexOf ('/');
593 file_name
= Uri
.UnescapeDataString (uri
.LocalPath
.Substring (last
+ 1));
598 void ProcessMethod ()
600 ServicePoint sp
= GetServicePoint ();
602 if (method
!= WebRequestMethods
.Ftp
.DownloadFile
)
603 throw new NotSupportedException ("FTP+proxy only supports RETR");
605 HttpWebRequest req
= (HttpWebRequest
) WebRequest
.Create (proxy
.GetProxy (requestUri
));
606 req
.Address
= requestUri
;
607 requestState
= RequestState
.Finished
;
608 WebResponse response
= req
.GetResponse ();
609 ftpResponse
.Stream
= new FtpDataStream (this, response
.GetResponseStream (), true);
610 ftpResponse
.StatusCode
= FtpStatusCode
.CommandOK
;
613 State
= RequestState
.Connecting
;
617 OpenControlConnection ();
618 CWDAndSetFileName (requestUri
);
622 // Open data connection and receive data
623 case WebRequestMethods
.Ftp
.DownloadFile
:
624 case WebRequestMethods
.Ftp
.ListDirectory
:
625 case WebRequestMethods
.Ftp
.ListDirectoryDetails
:
628 // Open data connection and send data
629 case WebRequestMethods
.Ftp
.AppendFile
:
630 case WebRequestMethods
.Ftp
.UploadFile
:
631 case WebRequestMethods
.Ftp
.UploadFileWithUniqueName
:
634 // Get info from control connection
635 case WebRequestMethods
.Ftp
.GetFileSize
:
636 case WebRequestMethods
.Ftp
.GetDateTimestamp
:
637 case WebRequestMethods
.Ftp
.PrintWorkingDirectory
:
638 case WebRequestMethods
.Ftp
.MakeDirectory
:
639 case WebRequestMethods
.Ftp
.Rename
:
640 case WebRequestMethods
.Ftp
.DeleteFile
:
641 ProcessSimpleMethod ();
643 default: // What to do here?
644 throw new Exception (String
.Format ("Support for command {0} not implemented yet", method
));
650 private void CloseControlConnection () {
651 if (controlStream
!= null) {
652 SendCommand (QuitCommand
);
653 controlStream
.Close ();
654 controlStream
= null;
658 internal void CloseDataConnection () {
659 if(origDataStream
!= null) {
660 origDataStream
.Close ();
661 origDataStream
= null;
665 private void CloseConnection () {
666 CloseControlConnection ();
667 CloseDataConnection ();
670 void ProcessSimpleMethod ()
672 State
= RequestState
.TransferInProgress
;
676 if (method
== WebRequestMethods
.Ftp
.PrintWorkingDirectory
)
679 if (method
== WebRequestMethods
.Ftp
.Rename
)
680 method
= RenameFromCommand
;
682 status
= SendCommand (method
, file_name
);
684 ftpResponse
.Stream
= Stream
.Null
;
686 string desc
= status
.StatusDescription
;
689 case WebRequestMethods
.Ftp
.GetFileSize
: {
690 if (status
.StatusCode
!= FtpStatusCode
.FileStatus
)
691 throw CreateExceptionFromResponse (status
);
695 for (i
= 4, len
= 0; i
< desc
.Length
&& Char
.IsDigit (desc
[i
]); i
++, len
++)
699 throw new WebException ("Bad format for server response in " + method
);
701 if (!Int64
.TryParse (desc
.Substring (4, len
), out size
))
702 throw new WebException ("Bad format for server response in " + method
);
704 ftpResponse
.contentLength
= size
;
707 case WebRequestMethods
.Ftp
.GetDateTimestamp
:
708 if (status
.StatusCode
!= FtpStatusCode
.FileStatus
)
709 throw CreateExceptionFromResponse (status
);
710 ftpResponse
.LastModified
= DateTime
.ParseExact (desc
.Substring (4), "yyyyMMddHHmmss", null);
712 case WebRequestMethods
.Ftp
.MakeDirectory
:
713 if (status
.StatusCode
!= FtpStatusCode
.PathnameCreated
)
714 throw CreateExceptionFromResponse (status
);
717 method
= WebRequestMethods
.Ftp
.PrintWorkingDirectory
;
719 if (status
.StatusCode
!= FtpStatusCode
.FileActionOK
)
720 throw CreateExceptionFromResponse (status
);
722 status
= SendCommand (method
);
724 if (status
.StatusCode
!= FtpStatusCode
.PathnameCreated
)
725 throw CreateExceptionFromResponse (status
);
727 case RenameFromCommand
:
728 method
= WebRequestMethods
.Ftp
.Rename
;
729 if (status
.StatusCode
!= FtpStatusCode
.FileCommandPending
)
730 throw CreateExceptionFromResponse (status
);
731 // Pass an empty string if RenameTo wasn't specified
732 status
= SendCommand (RenameToCommand
, renameTo
!= null ? renameTo
: String
.Empty
);
733 if (status
.StatusCode
!= FtpStatusCode
.FileActionOK
)
734 throw CreateExceptionFromResponse (status
);
736 case WebRequestMethods
.Ftp
.DeleteFile
:
737 if (status
.StatusCode
!= FtpStatusCode
.FileActionOK
) {
738 throw CreateExceptionFromResponse (status
);
743 State
= RequestState
.Finished
;
748 State
= RequestState
.OpeningData
;
750 OpenDataConnection ();
752 State
= RequestState
.TransferInProgress
;
753 requestStream
= new FtpDataStream (this, dataStream
, false);
754 asyncResult
.Stream
= requestStream
;
759 State
= RequestState
.OpeningData
;
761 OpenDataConnection ();
763 State
= RequestState
.TransferInProgress
;
764 ftpResponse
.Stream
= new FtpDataStream (this, dataStream
, true);
767 void CheckRequestStarted ()
769 if (State
!= RequestState
.Before
)
770 throw new InvalidOperationException ("There is a request currently in progress");
773 void OpenControlConnection ()
775 Exception exception
= null;
777 foreach (IPAddress address
in hostEntry
.AddressList
) {
778 sock
= new Socket (address
.AddressFamily
, SocketType
.Stream
, ProtocolType
.Tcp
);
780 IPEndPoint remote
= new IPEndPoint (address
, requestUri
.Port
);
782 if (!ServicePoint
.CallEndPointDelegate (sock
, remote
)) {
787 sock
.Connect (remote
);
788 localEndPoint
= (IPEndPoint
) sock
.LocalEndPoint
;
790 } catch (SocketException exc
) {
798 // Couldn't connect to any address
800 throw new WebException ("Unable to connect to remote server", exception
,
801 WebExceptionStatus
.UnknownError
, ftpResponse
);
803 controlStream
= new NetworkStream (sock
);
804 controlReader
= new StreamReader (controlStream
, Encoding
.ASCII
);
806 State
= RequestState
.Authenticating
;
809 FtpStatus status
= SendCommand ("OPTS", "utf8", "on");
810 if ((int)status
.StatusCode
< 200 || (int)status
.StatusCode
> 300)
811 dataEncoding
= Encoding
.Default
;
813 dataEncoding
= Encoding
.UTF8
;
815 status
= SendCommand (WebRequestMethods
.Ftp
.PrintWorkingDirectory
);
816 initial_path
= GetInitialPath (status
);
819 static string GetInitialPath (FtpStatus status
)
821 int s
= (int) status
.StatusCode
;
822 if (s
< 200 || s
> 300 || status
.StatusDescription
.Length
<= 4)
823 throw new WebException ("Error getting current directory: " + status
.StatusDescription
, null,
824 WebExceptionStatus
.UnknownError
, null);
826 string msg
= status
.StatusDescription
.Substring (4);
827 if (msg
[0] == '"') {
828 int next_quote
= msg
.IndexOf ('\"', 1);
829 if (next_quote
== -1)
830 throw new WebException ("Error getting current directory: PWD -> " + status
.StatusDescription
, null,
831 WebExceptionStatus
.UnknownError
, null);
833 msg
= msg
.Substring (1, next_quote
- 1);
836 if (!msg
.EndsWith ("/"))
841 // Probably we could do better having here a regex
842 Socket
SetupPassiveConnection (string statusDescription
)
844 // Current response string
845 string response
= statusDescription
;
846 if (response
.Length
< 4)
847 throw new WebException ("Cannot open passive data connection");
849 // Look for first digit after code
851 for (i
= 3; i
< response
.Length
&& !Char
.IsDigit (response
[i
]); i
++)
853 if (i
>= response
.Length
)
854 throw new WebException ("Cannot open passive data connection");
857 string [] digits
= response
.Substring (i
).Split (new char [] {','}
, 6);
858 if (digits
.Length
!= 6)
859 throw new WebException ("Cannot open passive data connection");
861 // Clean non-digits at the end of last element
863 for (j
= digits
[5].Length
- 1; j
>= 0 && !Char
.IsDigit (digits
[5][j
]); j
--)
866 throw new WebException ("Cannot open passive data connection");
868 digits
[5] = digits
[5].Substring (0, j
+ 1);
872 ip
= IPAddress
.Parse (String
.Join (".", digits
, 0, 4));
873 } catch (FormatException
) {
874 throw new WebException ("Cannot open passive data connection");
879 if (!Int32
.TryParse (digits
[4], out p1
) || !Int32
.TryParse (digits
[5], out p2
))
880 throw new WebException ("Cannot open passive data connection");
882 port
= (p1
<< 8) + p2
; // p1 * 256 + p2
883 //port = p1 * 256 + p2;
884 if (port
< IPEndPoint
.MinPort
|| port
> IPEndPoint
.MaxPort
)
885 throw new WebException ("Cannot open passive data connection");
887 IPEndPoint ep
= new IPEndPoint (ip
, port
);
888 Socket sock
= new Socket (ep
.AddressFamily
, SocketType
.Stream
, ProtocolType
.Tcp
);
891 } catch (SocketException
) {
893 throw new WebException ("Cannot open passive data connection");
899 Exception
CreateExceptionFromResponse (FtpStatus status
)
901 FtpWebResponse ftpResponse
= new FtpWebResponse (this, requestUri
, method
, status
);
903 WebException exc
= new WebException ("Server returned an error: " + status
.StatusDescription
,
904 null, WebExceptionStatus
.ProtocolError
, ftpResponse
);
908 // Here we could also get a server error, so be cautious
909 internal void SetTransferCompleted ()
914 State
= RequestState
.Finished
;
915 FtpStatus status
= GetResponseStatus ();
916 ftpResponse
.UpdateStatus (status
);
921 internal void OperationCompleted ()
927 void SetCompleteWithError (Exception exc
)
929 if (asyncResult
!= null) {
930 asyncResult
.SetCompleted (false, exc
);
934 Socket
InitDataConnection ()
939 status
= SendCommand (PassiveCommand
);
940 if (status
.StatusCode
!= FtpStatusCode
.EnteringPassive
) {
941 throw CreateExceptionFromResponse (status
);
944 return SetupPassiveConnection (status
.StatusDescription
);
947 // Open a socket to listen the server's connection
948 Socket sock
= new Socket (AddressFamily
.InterNetwork
, SocketType
.Stream
, ProtocolType
.Tcp
);
950 sock
.Bind (new IPEndPoint (localEndPoint
.Address
, 0));
951 sock
.Listen (1); // We only expect a connection from server
953 } catch (SocketException e
) {
956 throw new WebException ("Couldn't open listening socket on client", e
);
959 IPEndPoint ep
= (IPEndPoint
) sock
.LocalEndPoint
;
960 string ipString
= ep
.Address
.ToString ().Replace ('.', ',');
961 int h1
= ep
.Port
>> 8; // ep.Port / 256
962 int h2
= ep
.Port
% 256;
964 string portParam
= ipString
+ "," + h1
+ "," + h2
;
965 status
= SendCommand (PortCommand
, portParam
);
967 if (status
.StatusCode
!= FtpStatusCode
.CommandOK
) {
969 throw (CreateExceptionFromResponse (status
));
975 void OpenDataConnection ()
979 Socket s
= InitDataConnection ();
981 // Handle content offset
983 status
= SendCommand (RestCommand
, offset
.ToString ());
984 if (status
.StatusCode
!= FtpStatusCode
.FileCommandPending
)
985 throw CreateExceptionFromResponse (status
);
988 if (method
!= WebRequestMethods
.Ftp
.ListDirectory
&& method
!= WebRequestMethods
.Ftp
.ListDirectoryDetails
&&
989 method
!= WebRequestMethods
.Ftp
.UploadFileWithUniqueName
) {
990 status
= SendCommand (method
, file_name
);
992 status
= SendCommand (method
);
995 if (status
.StatusCode
!= FtpStatusCode
.OpeningData
&& status
.StatusCode
!= FtpStatusCode
.DataAlreadyOpen
)
996 throw CreateExceptionFromResponse (status
);
999 origDataStream
= new NetworkStream (s
, true);
1000 dataStream
= origDataStream
;
1002 ChangeToSSLSocket (ref dataStream
);
1006 // Active connection (use Socket.Blocking to true)
1007 Socket incoming
= null;
1009 incoming
= s
.Accept ();
1011 catch (SocketException
) {
1013 if (incoming
!= null)
1016 throw new ProtocolViolationException ("Server commited a protocol violation.");
1020 origDataStream
= new NetworkStream (incoming
, true);
1021 dataStream
= origDataStream
;
1023 ChangeToSSLSocket (ref dataStream
);
1026 ftpResponse
.UpdateStatus (status
);
1029 void Authenticate ()
1031 string username
= null;
1032 string password
= null;
1033 string domain
= null;
1035 if (credentials
!= null) {
1036 username
= credentials
.UserName
;
1037 password
= credentials
.Password
;
1038 domain
= credentials
.Domain
;
1041 if (username
== null)
1042 username
= "anonymous";
1043 if (password
== null)
1044 password
= "@anonymous";
1045 if (!string.IsNullOrEmpty (domain
))
1046 username
= domain
+ '\\' + username
;
1048 // Connect to server and get banner message
1049 FtpStatus status
= GetResponseStatus ();
1050 ftpResponse
.BannerMessage
= status
.StatusDescription
;
1053 InitiateSecureConnection (ref controlStream
);
1054 controlReader
= new StreamReader (controlStream
, Encoding
.ASCII
);
1055 status
= SendCommand ("PBSZ", "0");
1056 int st
= (int) status
.StatusCode
;
1057 if (st
< 200 || st
>= 300)
1058 throw CreateExceptionFromResponse (status
);
1059 // TODO: what if "PROT P" is denied by the server? What does MS do?
1060 status
= SendCommand ("PROT", "P");
1061 st
= (int) status
.StatusCode
;
1062 if (st
< 200 || st
>= 300)
1063 throw CreateExceptionFromResponse (status
);
1065 status
= new FtpStatus (FtpStatusCode
.SendUserCommand
, "");
1068 if (status
.StatusCode
!= FtpStatusCode
.SendUserCommand
)
1069 throw CreateExceptionFromResponse (status
);
1071 status
= SendCommand (UserCommand
, username
);
1073 switch (status
.StatusCode
) {
1074 case FtpStatusCode
.SendPasswordCommand
:
1075 status
= SendCommand (PasswordCommand
, password
);
1076 if (status
.StatusCode
!= FtpStatusCode
.LoggedInProceed
)
1077 throw CreateExceptionFromResponse (status
);
1079 case FtpStatusCode
.LoggedInProceed
:
1082 throw CreateExceptionFromResponse (status
);
1085 ftpResponse
.WelcomeMessage
= status
.StatusDescription
;
1086 ftpResponse
.UpdateStatus (status
);
1089 FtpStatus
SendCommand (string command
, params string [] parameters
) {
1090 return SendCommand (true, command
, parameters
);
1093 FtpStatus
SendCommand (bool waitResponse
, string command
, params string [] parameters
)
1096 string commandString
= command
;
1097 if (parameters
.Length
> 0)
1098 commandString
+= " " + String
.Join (" ", parameters
);
1100 commandString
+= EOL
;
1101 cmd
= dataEncoding
.GetBytes (commandString
);
1103 controlStream
.Write (cmd
, 0, cmd
.Length
);
1104 } catch (IOException
) {
1105 //controlStream.Close ();
1106 return new FtpStatus(FtpStatusCode
.ServiceNotAvailable
, "Write failed");
1112 FtpStatus result
= GetResponseStatus ();
1113 if (ftpResponse
!= null)
1114 ftpResponse
.UpdateStatus (result
);
1118 internal static FtpStatus
ServiceNotAvailable ()
1120 return new FtpStatus (FtpStatusCode
.ServiceNotAvailable
, Locale
.GetText ("Invalid response from server"));
1123 internal FtpStatus
GetResponseStatus ()
1126 string response
= null;
1129 response
= controlReader
.ReadLine ();
1130 } catch (IOException
) {
1133 if (response
== null || response
.Length
< 3)
1134 return ServiceNotAvailable ();
1137 if (!Int32
.TryParse (response
.Substring (0, 3), out code
))
1138 return ServiceNotAvailable ();
1140 if (response
.Length
> 3 && response
[3] == '-'){
1142 string find
= code
.ToString() + ' ';
1146 line
= controlReader
.ReadLine();
1147 } catch (IOException
) {
1150 return ServiceNotAvailable ();
1152 response
+= Environment
.NewLine
+ line
;
1154 if (line
.StartsWith(find
, StringComparison
.Ordinal
))
1158 return new FtpStatus ((FtpStatusCode
) code
, response
);
1162 private void InitiateSecureConnection (ref Stream stream
) {
1163 FtpStatus status
= SendCommand (AuthCommand
, "TLS");
1164 if (status
.StatusCode
!= FtpStatusCode
.ServerWantsSecureSession
)
1165 throw CreateExceptionFromResponse (status
);
1167 ChangeToSSLSocket (ref stream
);
1170 internal bool ChangeToSSLSocket (ref Stream stream
) {
1172 var provider
= MonoTlsProviderFactory
.GetProviderInternal ();
1173 var settings
= MSI
.MonoTlsSettings
.CopyDefaultSettings ();
1174 settings
.UseServicePointManagerCallback
= true;
1175 var sslStream
= provider
.CreateSslStream (stream
, true, settings
);
1176 sslStream
.AuthenticateAsClient (requestUri
.Host
, null, SslProtocols
.Default
, false);
1177 stream
= sslStream
.AuthenticatedStream
;
1180 throw new NotImplementedException ();
1184 bool InFinalState () {
1185 return (State
== RequestState
.Aborted
|| State
== RequestState
.Error
|| State
== RequestState
.Finished
);
1188 bool InProgress () {
1189 return (State
!= RequestState
.Before
&& !InFinalState ());
1192 internal void CheckIfAborted () {
1193 if (State
== RequestState
.Aborted
)
1194 throw new WebException ("Request aborted", WebExceptionStatus
.RequestCanceled
);
1197 void CheckFinalState () {
1198 if (InFinalState ())
1199 throw new InvalidOperationException ("Cannot change final state");