2 // System.Net.FtpWebRequest.cs
5 // Carlos Alberto Cortez (calberto.cortez@gmail.com)
7 // (c) Copyright 2006 Novell, Inc. (http://www.novell.com)
13 using System.Net.Sockets;
15 using System.Threading;
16 using System.Net.Cache;
17 using System.Security.Cryptography.X509Certificates;
19 using System.Net.Security;
20 using System.Security.Authentication;
24 public sealed class FtpWebRequest : WebRequest
27 string file_name; // By now, used for upload
28 ServicePoint servicePoint;
31 StreamReader controlReader;
32 NetworkCredential credentials;
33 IPHostEntry hostEntry;
34 IPEndPoint localEndPoint;
37 int rwTimeout = 300000;
40 bool enableSsl = false;
41 bool usePassive = true;
42 bool keepAlive = false;
43 string method = WebRequestMethods.Ftp.DownloadFile;
45 object locker = new object ();
47 RequestState requestState = RequestState.Before;
48 FtpAsyncResult asyncResult;
49 FtpWebResponse ftpResponse;
53 const string ChangeDir = "CWD";
54 const string UserCommand = "USER";
55 const string PasswordCommand = "PASS";
56 const string TypeCommand = "TYPE";
57 const string PassiveCommand = "PASV";
58 const string PortCommand = "PORT";
59 const string AbortCommand = "ABOR";
60 const string AuthCommand = "AUTH";
61 const string RestCommand = "REST";
62 const string RenameFromCommand = "RNFR";
63 const string RenameToCommand = "RNTO";
64 const string QuitCommand = "QUIT";
65 const string EOL = "\r\n"; // Special end of line
81 static readonly string [] supportedCommands = new string [] {
82 WebRequestMethods.Ftp.AppendFile, // APPE
83 WebRequestMethods.Ftp.DeleteFile, // DELE
84 WebRequestMethods.Ftp.ListDirectoryDetails, // LIST
85 WebRequestMethods.Ftp.GetDateTimestamp, // MDTM
86 WebRequestMethods.Ftp.MakeDirectory, // MKD
87 WebRequestMethods.Ftp.ListDirectory, // NLST
88 WebRequestMethods.Ftp.PrintWorkingDirectory, // PWD
89 WebRequestMethods.Ftp.Rename, // RENAME
90 WebRequestMethods.Ftp.DownloadFile, // RETR
91 WebRequestMethods.Ftp.RemoveDirectory, // RMD
92 WebRequestMethods.Ftp.GetFileSize, // SIZE
93 WebRequestMethods.Ftp.UploadFile, // STOR
94 WebRequestMethods.Ftp.UploadFileWithUniqueName // STUR
97 internal FtpWebRequest (Uri uri)
99 this.requestUri = uri;
100 this.proxy = GlobalProxySelection.Select;
103 static Exception GetMustImplement ()
105 return new NotImplementedException ();
109 public X509CertificateCollection ClientCertificates
112 throw GetMustImplement ();
115 throw GetMustImplement ();
120 public override string ConnectionGroupName
123 throw GetMustImplement ();
126 throw GetMustImplement ();
130 public override string ContentType {
132 throw new NotSupportedException ();
135 throw new NotSupportedException ();
139 public override long ContentLength {
148 public long ContentOffset {
153 CheckRequestStarted ();
155 throw new ArgumentOutOfRangeException ();
161 public override ICredentials Credentials {
166 CheckRequestStarted ();
168 throw new ArgumentNullException ();
169 if (!(value is NetworkCredential))
170 throw new ArgumentException ();
172 credentials = value as NetworkCredential;
177 public static new RequestCachePolicy DefaultCachePolicy
180 throw GetMustImplement ();
183 throw GetMustImplement ();
187 public bool EnableSsl {
192 CheckRequestStarted ();
198 public override WebHeaderCollection Headers
201 throw GetMustImplement ();
204 throw GetMustImplement ();
208 [MonoTODO ("We don't support KeepAlive = true")]
209 public bool KeepAlive {
214 CheckRequestStarted ();
219 public override string Method {
224 CheckRequestStarted ();
226 throw new ArgumentNullException ("Method string cannot be null");
228 if (value.Length == 0 || Array.BinarySearch (supportedCommands, value) < 0)
229 throw new ArgumentException ("Method not supported", "value");
235 public override bool PreAuthenticate {
237 throw new NotSupportedException ();
240 throw new NotSupportedException ();
244 public override IWebProxy Proxy {
249 CheckRequestStarted ();
251 throw new ArgumentNullException ();
257 public int ReadWriteTimeout {
262 CheckRequestStarted ();
265 throw new ArgumentOutOfRangeException ();
271 public string RenameTo {
276 CheckRequestStarted ();
277 if (value == null || value.Length == 0)
278 throw new ArgumentException ("RenameTo value can't be null or empty", "RenameTo");
284 public override Uri RequestUri {
290 public ServicePoint ServicePoint {
292 return GetServicePoint ();
296 public bool UsePassive {
301 CheckRequestStarted ();
307 public override bool UseDefaultCredentials
310 throw GetMustImplement ();
313 throw GetMustImplement ();
317 public bool UseBinary {
321 CheckRequestStarted ();
326 public override int Timeout {
331 CheckRequestStarted ();
334 throw new ArgumentOutOfRangeException ();
342 return binary ? "I" : "A";
357 requestState = value;
362 public override void Abort () {
364 if (State == RequestState.TransferInProgress) {
365 /*FtpStatus status = */
366 SendCommand (false, AbortCommand);
369 if (!InFinalState ()) {
370 State = RequestState.Aborted;
371 ftpResponse = new FtpWebResponse (this, requestUri, method, FtpStatusCode.FileActionAborted, "Aborted by request");
376 public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state) {
377 if (asyncResult != null && !asyncResult.IsCompleted) {
378 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
383 asyncResult = new FtpAsyncResult (callback, state);
387 asyncResult.SetCompleted (true, ftpResponse);
389 if (State == RequestState.Before)
390 State = RequestState.Scheduled;
392 Thread thread = new Thread (ProcessRequest);
400 public override WebResponse EndGetResponse (IAsyncResult asyncResult) {
401 if (asyncResult == null)
402 throw new ArgumentNullException ("AsyncResult cannot be null!");
404 if (!(asyncResult is FtpAsyncResult) || asyncResult != this.asyncResult)
405 throw new ArgumentException ("AsyncResult is from another request!");
407 FtpAsyncResult asyncFtpResult = (FtpAsyncResult) asyncResult;
408 if (!asyncFtpResult.WaitUntilComplete (timeout, false)) {
410 throw new WebException ("Transfer timed out.", WebExceptionStatus.Timeout);
417 if (asyncFtpResult.GotException)
418 throw asyncFtpResult.Exception;
420 return asyncFtpResult.Response;
423 public override WebResponse GetResponse () {
424 IAsyncResult asyncResult = BeginGetResponse (null, null);
425 return EndGetResponse (asyncResult);
428 public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state) {
429 if (method != WebRequestMethods.Ftp.UploadFile && method != WebRequestMethods.Ftp.UploadFileWithUniqueName &&
430 method != WebRequestMethods.Ftp.AppendFile)
431 throw new ProtocolViolationException ();
436 if (State != RequestState.Before)
437 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
439 State = RequestState.Scheduled;
442 asyncResult = new FtpAsyncResult (callback, state);
443 Thread thread = new Thread (ProcessRequest);
449 public override Stream EndGetRequestStream (IAsyncResult asyncResult) {
450 if (asyncResult == null)
451 throw new ArgumentNullException ("asyncResult");
453 if (!(asyncResult is FtpAsyncResult))
454 throw new ArgumentException ("asyncResult");
456 if (State == RequestState.Aborted) {
457 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
460 if (asyncResult != this.asyncResult)
461 throw new ArgumentException ("AsyncResult is from another request!");
463 FtpAsyncResult res = (FtpAsyncResult) asyncResult;
465 if (!res.WaitUntilComplete (timeout, false)) {
467 throw new WebException ("Request timed out");
470 if (res.GotException)
476 public override Stream GetRequestStream () {
477 IAsyncResult asyncResult = BeginGetRequestStream (null, null);
478 return EndGetRequestStream (asyncResult);
481 ServicePoint GetServicePoint ()
483 if (servicePoint == null)
484 servicePoint = ServicePointManager.FindServicePoint (requestUri, proxy);
489 // Probably move some code of command connection here
493 hostEntry = GetServicePoint ().HostEntry;
495 if (hostEntry == null) {
496 ftpResponse.UpdateStatus (new FtpStatus(FtpStatusCode.ActionAbortedLocalProcessingError, "Cannot resolve server name"));
497 throw new WebException ("The remote server name could not be resolved: " + requestUri,
498 null, WebExceptionStatus.NameResolutionFailure, ftpResponse);
502 void ProcessRequest () {
504 if (State == RequestState.Scheduled) {
505 ftpResponse = new FtpWebResponse (this, requestUri, method, keepAlive);
509 //State = RequestState.Finished;
510 //finalResponse = ftpResponse;
511 asyncResult.SetCompleted (false, ftpResponse);
513 catch (Exception e) {
514 State = RequestState.Error;
515 SetCompleteWithError (e);
520 FtpStatus status = GetResponseStatus ();
522 ftpResponse.UpdateStatus (status);
524 if (ftpResponse.IsFinal ()) {
525 State = RequestState.Finished;
529 asyncResult.SetCompleted (false, ftpResponse);
536 FtpStatus status = SendCommand (TypeCommand, DataType);
537 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
538 throw CreateExceptionFromResponse (status);
542 string GetRemoteFolderPath (Uri uri)
545 string local_path = Uri.UnescapeDataString (uri.LocalPath);
546 if (initial_path == null || initial_path == "/") {
549 if (local_path [0] == '/')
550 local_path = local_path.Substring (1);
552 Uri initial = new Uri ("ftp://dummy-host" + initial_path);
553 result = new Uri (initial, local_path).LocalPath;
556 int last = result.LastIndexOf ('/');
560 return result.Substring (0, last + 1);
563 void CWDAndSetFileName (Uri uri)
565 string remote_folder = GetRemoteFolderPath (uri);
567 if (remote_folder != null) {
568 status = SendCommand (ChangeDir, remote_folder);
569 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
570 throw CreateExceptionFromResponse (status);
572 int last = uri.LocalPath.LastIndexOf ('/');
574 file_name = Uri.UnescapeDataString (uri.LocalPath.Substring (last + 1));
579 void ProcessMethod ()
581 State = RequestState.Connecting;
585 OpenControlConnection ();
586 CWDAndSetFileName (requestUri);
590 // Open data connection and receive data
591 case WebRequestMethods.Ftp.DownloadFile:
592 case WebRequestMethods.Ftp.ListDirectory:
593 case WebRequestMethods.Ftp.ListDirectoryDetails:
596 // Open data connection and send data
597 case WebRequestMethods.Ftp.AppendFile:
598 case WebRequestMethods.Ftp.UploadFile:
599 case WebRequestMethods.Ftp.UploadFileWithUniqueName:
602 // Get info from control connection
603 case WebRequestMethods.Ftp.GetFileSize:
604 case WebRequestMethods.Ftp.GetDateTimestamp:
605 case WebRequestMethods.Ftp.PrintWorkingDirectory:
606 case WebRequestMethods.Ftp.MakeDirectory:
607 case WebRequestMethods.Ftp.Rename:
608 case WebRequestMethods.Ftp.DeleteFile:
609 ProcessSimpleMethod ();
611 default: // What to do here?
612 throw new Exception (String.Format ("Support for command {0} not implemented yet", method));
618 private void CloseControlConnection () {
619 if (controlStream != null) {
620 SendCommand (QuitCommand);
621 controlStream.Close ();
622 controlStream = null;
626 private void CloseDataConnection () {
627 if(dataStream != null) {
633 private void CloseConnection () {
634 CloseControlConnection ();
635 CloseDataConnection ();
638 void ProcessSimpleMethod ()
640 State = RequestState.TransferInProgress;
644 if (method == WebRequestMethods.Ftp.PrintWorkingDirectory)
647 if (method == WebRequestMethods.Ftp.Rename)
648 method = RenameFromCommand;
650 status = SendCommand (method, file_name);
652 ftpResponse.Stream = Stream.Null;
654 string desc = status.StatusDescription;
657 case WebRequestMethods.Ftp.GetFileSize: {
658 if (status.StatusCode != FtpStatusCode.FileStatus)
659 throw CreateExceptionFromResponse (status);
663 for (i = 4, len = 0; i < desc.Length && Char.IsDigit (desc [i]); i++, len++)
667 throw new WebException ("Bad format for server response in " + method);
669 if (!Int64.TryParse (desc.Substring (4, len), out size))
670 throw new WebException ("Bad format for server response in " + method);
672 ftpResponse.contentLength = size;
675 case WebRequestMethods.Ftp.GetDateTimestamp:
676 if (status.StatusCode != FtpStatusCode.FileStatus)
677 throw CreateExceptionFromResponse (status);
678 ftpResponse.LastModified = DateTime.ParseExact (desc.Substring (4), "yyyyMMddHHmmss", null);
680 case WebRequestMethods.Ftp.MakeDirectory:
681 if (status.StatusCode != FtpStatusCode.PathnameCreated)
682 throw CreateExceptionFromResponse (status);
685 method = WebRequestMethods.Ftp.PrintWorkingDirectory;
687 if (status.StatusCode != FtpStatusCode.FileActionOK)
688 throw CreateExceptionFromResponse (status);
690 status = SendCommand (method);
692 if (status.StatusCode != FtpStatusCode.PathnameCreated)
693 throw CreateExceptionFromResponse (status);
695 case RenameFromCommand:
696 method = WebRequestMethods.Ftp.Rename;
697 if (status.StatusCode != FtpStatusCode.FileCommandPending)
698 throw CreateExceptionFromResponse (status);
699 // Pass an empty string if RenameTo wasn't specified
700 status = SendCommand (RenameToCommand, renameTo != null ? renameTo : String.Empty);
701 if (status.StatusCode != FtpStatusCode.FileActionOK)
702 throw CreateExceptionFromResponse (status);
704 case WebRequestMethods.Ftp.DeleteFile:
705 if (status.StatusCode != FtpStatusCode.FileActionOK) {
706 throw CreateExceptionFromResponse (status);
711 State = RequestState.Finished;
716 State = RequestState.OpeningData;
718 OpenDataConnection ();
720 State = RequestState.TransferInProgress;
721 requestStream = new FtpDataStream (this, dataStream, false);
722 asyncResult.Stream = requestStream;
727 State = RequestState.OpeningData;
729 OpenDataConnection ();
731 State = RequestState.TransferInProgress;
732 ftpResponse.Stream = new FtpDataStream (this, dataStream, true);
735 void CheckRequestStarted ()
737 if (State != RequestState.Before)
738 throw new InvalidOperationException ("There is a request currently in progress");
741 void OpenControlConnection ()
743 Exception exception = null;
745 foreach (IPAddress address in hostEntry.AddressList) {
746 sock = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
748 IPEndPoint remote = new IPEndPoint (address, requestUri.Port);
750 if (!ServicePoint.CallEndPointDelegate (sock, remote)) {
755 sock.Connect (remote);
756 localEndPoint = (IPEndPoint) sock.LocalEndPoint;
758 } catch (SocketException exc) {
766 // Couldn't connect to any address
768 throw new WebException ("Unable to connect to remote server", exception,
769 WebExceptionStatus.UnknownError, ftpResponse);
771 controlStream = new NetworkStream (sock);
772 controlReader = new StreamReader (controlStream, Encoding.ASCII);
774 State = RequestState.Authenticating;
777 FtpStatus status = SendCommand ("OPTS", "utf8", "on");
778 // ignore status for OPTS
779 status = SendCommand (WebRequestMethods.Ftp.PrintWorkingDirectory);
780 initial_path = GetInitialPath (status);
783 static string GetInitialPath (FtpStatus status)
785 int s = (int) status.StatusCode;
786 if (s < 200 || s > 300 || status.StatusDescription.Length <= 4)
787 throw new WebException ("Error getting current directory: " + status.StatusDescription, null,
788 WebExceptionStatus.UnknownError, null);
790 string msg = status.StatusDescription.Substring (4);
791 if (msg [0] == '"') {
792 int next_quote = msg.IndexOf ('\"', 1);
793 if (next_quote == -1)
794 throw new WebException ("Error getting current directory: PWD -> " + status.StatusDescription, null,
795 WebExceptionStatus.UnknownError, null);
797 msg = msg.Substring (1, next_quote - 1);
800 if (!msg.EndsWith ("/"))
805 // Probably we could do better having here a regex
806 Socket SetupPassiveConnection (string statusDescription)
808 // Current response string
809 string response = statusDescription;
810 if (response.Length < 4)
811 throw new WebException ("Cannot open passive data connection");
813 // Look for first digit after code
815 for (i = 3; i < response.Length && !Char.IsDigit (response [i]); i++)
817 if (i >= response.Length)
818 throw new WebException ("Cannot open passive data connection");
821 string [] digits = response.Substring (i).Split (new char [] {','}, 6);
822 if (digits.Length != 6)
823 throw new WebException ("Cannot open passive data connection");
825 // Clean non-digits at the end of last element
827 for (j = digits [5].Length - 1; j >= 0 && !Char.IsDigit (digits [5][j]); j--)
830 throw new WebException ("Cannot open passive data connection");
832 digits [5] = digits [5].Substring (0, j + 1);
836 ip = IPAddress.Parse (String.Join (".", digits, 0, 4));
837 } catch (FormatException) {
838 throw new WebException ("Cannot open passive data connection");
843 if (!Int32.TryParse (digits [4], out p1) || !Int32.TryParse (digits [5], out p2))
844 throw new WebException ("Cannot open passive data connection");
846 port = (p1 << 8) + p2; // p1 * 256 + p2
847 //port = p1 * 256 + p2;
848 if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
849 throw new WebException ("Cannot open passive data connection");
851 IPEndPoint ep = new IPEndPoint (ip, port);
852 Socket sock = new Socket (ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
855 } catch (SocketException) {
857 throw new WebException ("Cannot open passive data connection");
863 Exception CreateExceptionFromResponse (FtpStatus status)
865 FtpWebResponse ftpResponse = new FtpWebResponse (this, requestUri, method, status);
867 WebException exc = new WebException ("Server returned an error: " + status.StatusDescription,
868 null, WebExceptionStatus.ProtocolError, ftpResponse);
872 // Here we could also get a server error, so be cautious
873 internal void SetTransferCompleted ()
878 State = RequestState.Finished;
879 FtpStatus status = GetResponseStatus ();
880 ftpResponse.UpdateStatus (status);
885 internal void OperationCompleted ()
891 void SetCompleteWithError (Exception exc)
893 if (asyncResult != null) {
894 asyncResult.SetCompleted (false, exc);
898 Socket InitDataConnection ()
903 status = SendCommand (PassiveCommand);
904 if (status.StatusCode != FtpStatusCode.EnteringPassive) {
905 throw CreateExceptionFromResponse (status);
908 return SetupPassiveConnection (status.StatusDescription);
911 // Open a socket to listen the server's connection
912 Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
914 sock.Bind (new IPEndPoint (localEndPoint.Address, 0));
915 sock.Listen (1); // We only expect a connection from server
917 } catch (SocketException e) {
920 throw new WebException ("Couldn't open listening socket on client", e);
923 IPEndPoint ep = (IPEndPoint) sock.LocalEndPoint;
924 string ipString = ep.Address.ToString ().Replace ('.', ',');
925 int h1 = ep.Port >> 8; // ep.Port / 256
926 int h2 = ep.Port % 256;
928 string portParam = ipString + "," + h1 + "," + h2;
929 status = SendCommand (PortCommand, portParam);
931 if (status.StatusCode != FtpStatusCode.CommandOK) {
933 throw (CreateExceptionFromResponse (status));
939 void OpenDataConnection ()
943 Socket s = InitDataConnection ();
945 // Handle content offset
947 status = SendCommand (RestCommand, offset.ToString ());
948 if (status.StatusCode != FtpStatusCode.FileCommandPending)
949 throw CreateExceptionFromResponse (status);
952 if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails &&
953 method != WebRequestMethods.Ftp.UploadFileWithUniqueName) {
954 status = SendCommand (method, file_name);
956 status = SendCommand (method);
959 if (status.StatusCode != FtpStatusCode.OpeningData && status.StatusCode != FtpStatusCode.DataAlreadyOpen)
960 throw CreateExceptionFromResponse (status);
963 dataStream = new NetworkStream (s, false);
965 ChangeToSSLSocket (ref dataStream);
969 // Active connection (use Socket.Blocking to true)
970 Socket incoming = null;
972 incoming = s.Accept ();
974 catch (SocketException) {
976 if (incoming != null)
979 throw new ProtocolViolationException ("Server commited a protocol violation.");
983 dataStream = new NetworkStream (incoming, false);
985 ChangeToSSLSocket (ref dataStream);
988 ftpResponse.UpdateStatus (status);
993 string username = null;
994 string password = null;
995 string domain = null;
997 if (credentials != null) {
998 username = credentials.UserName;
999 password = credentials.Password;
1000 domain = credentials.Domain;
1003 if (username == null)
1004 username = "anonymous";
1005 if (password == null)
1006 password = "@anonymous";
1007 if (!string.IsNullOrEmpty (domain))
1008 username = domain + '\\' + username;
1010 // Connect to server and get banner message
1011 FtpStatus status = GetResponseStatus ();
1012 ftpResponse.BannerMessage = status.StatusDescription;
1015 InitiateSecureConnection (ref controlStream);
1016 controlReader = new StreamReader (controlStream, Encoding.ASCII);
1017 status = SendCommand ("PBSZ", "0");
1018 int st = (int) status.StatusCode;
1019 if (st < 200 || st >= 300)
1020 throw CreateExceptionFromResponse (status);
1021 // TODO: what if "PROT P" is denied by the server? What does MS do?
1022 status = SendCommand ("PROT", "P");
1023 st = (int) status.StatusCode;
1024 if (st < 200 || st >= 300)
1025 throw CreateExceptionFromResponse (status);
1027 status = new FtpStatus (FtpStatusCode.SendUserCommand, "");
1030 if (status.StatusCode != FtpStatusCode.SendUserCommand)
1031 throw CreateExceptionFromResponse (status);
1033 status = SendCommand (UserCommand, username);
1035 switch (status.StatusCode) {
1036 case FtpStatusCode.SendPasswordCommand:
1037 status = SendCommand (PasswordCommand, password);
1038 if (status.StatusCode != FtpStatusCode.LoggedInProceed)
1039 throw CreateExceptionFromResponse (status);
1041 case FtpStatusCode.LoggedInProceed:
1044 throw CreateExceptionFromResponse (status);
1047 ftpResponse.WelcomeMessage = status.StatusDescription;
1048 ftpResponse.UpdateStatus (status);
1051 FtpStatus SendCommand (string command, params string [] parameters) {
1052 return SendCommand (true, command, parameters);
1055 FtpStatus SendCommand (bool waitResponse, string command, params string [] parameters)
1058 string commandString = command;
1059 if (parameters.Length > 0)
1060 commandString += " " + String.Join (" ", parameters);
1062 commandString += EOL;
1063 cmd = Encoding.ASCII.GetBytes (commandString);
1065 controlStream.Write (cmd, 0, cmd.Length);
1066 } catch (IOException) {
1067 //controlStream.Close ();
1068 return new FtpStatus(FtpStatusCode.ServiceNotAvailable, "Write failed");
1074 FtpStatus result = GetResponseStatus ();
1075 if (ftpResponse != null)
1076 ftpResponse.UpdateStatus (result);
1080 internal static FtpStatus ServiceNotAvailable ()
1082 return new FtpStatus (FtpStatusCode.ServiceNotAvailable, Locale.GetText ("Invalid response from server"));
1085 internal FtpStatus GetResponseStatus ()
1088 string response = null;
1091 response = controlReader.ReadLine ();
1092 } catch (IOException) {
1095 if (response == null || response.Length < 3)
1096 return ServiceNotAvailable ();
1099 if (!Int32.TryParse (response.Substring (0, 3), out code))
1100 return ServiceNotAvailable ();
1102 if (response.Length > 3 && response [3] == '-'){
1104 string find = code.ToString() + ' ';
1107 line = controlReader.ReadLine();
1108 } catch (IOException) {
1111 return ServiceNotAvailable ();
1113 response += Environment.NewLine + line;
1115 if (line.StartsWith(find, StringComparison.Ordinal))
1119 return new FtpStatus ((FtpStatusCode) code, response);
1123 private void InitiateSecureConnection (ref Stream stream) {
1124 FtpStatus status = SendCommand (AuthCommand, "TLS");
1125 if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession)
1126 throw CreateExceptionFromResponse (status);
1128 ChangeToSSLSocket (ref stream);
1132 RemoteCertificateValidationCallback callback = delegate (object sender,
1133 X509Certificate certificate,
1135 SslPolicyErrors sslPolicyErrors) {
1136 // honor any exciting callback defined on ServicePointManager
1137 if (ServicePointManager.ServerCertificateValidationCallback != null)
1138 return ServicePointManager.ServerCertificateValidationCallback (sender, certificate, chain, sslPolicyErrors);
1139 // otherwise provide our own
1140 if (sslPolicyErrors != SslPolicyErrors.None)
1141 throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors);
1146 internal bool ChangeToSSLSocket (ref Stream stream) {
1148 stream.ChangeToSSLSocket ();
1151 SslStream sslStream = new SslStream (stream, true, callback, null);
1152 //sslStream.AuthenticateAsClient (Host, this.ClientCertificates, SslProtocols.Default, false);
1153 //TODO: client certificates
1154 sslStream.AuthenticateAsClient (requestUri.Host, null, SslProtocols.Default, false);
1158 throw new NotImplementedException ();
1162 bool InFinalState () {
1163 return (State == RequestState.Aborted || State == RequestState.Error || State == RequestState.Finished);
1166 bool InProgress () {
1167 return (State != RequestState.Before && !InFinalState ());
1170 internal void CheckIfAborted () {
1171 if (State == RequestState.Aborted)
1172 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
1175 void CheckFinalState () {
1176 if (InFinalState ())
1177 throw new InvalidOperationException ("Cannot change final state");