2 // System.Net.FtpWebRequest.cs
5 // Carlos Alberto Cortez (calberto.cortez@gmail.com)
7 // (c) Copyright 2006 Novell, Inc. (http://www.novell.com)
12 using System.Net.Sockets;
14 using System.Threading;
15 using System.Net.Cache;
16 using System.Security.Cryptography.X509Certificates;
18 using System.Net.Security;
19 using System.Security.Authentication;
23 public sealed class FtpWebRequest : WebRequest
26 string file_name; // By now, used for upload
27 ServicePoint servicePoint;
28 Stream origDataStream;
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;
178 public static new RequestCachePolicy DefaultCachePolicy
181 throw GetMustImplement ();
184 throw GetMustImplement ();
189 public bool EnableSsl {
194 CheckRequestStarted ();
200 public override WebHeaderCollection Headers
203 throw GetMustImplement ();
206 throw GetMustImplement ();
210 [MonoTODO ("We don't support KeepAlive = true")]
211 public bool KeepAlive {
216 CheckRequestStarted ();
221 public override string Method {
226 CheckRequestStarted ();
228 throw new ArgumentNullException ("Method string cannot be null");
230 if (value.Length == 0 || Array.BinarySearch (supportedCommands, value) < 0)
231 throw new ArgumentException ("Method not supported", "value");
237 public override bool PreAuthenticate {
239 throw new NotSupportedException ();
242 throw new NotSupportedException ();
246 public override IWebProxy Proxy {
251 CheckRequestStarted ();
256 public int ReadWriteTimeout {
261 CheckRequestStarted ();
264 throw new ArgumentOutOfRangeException ();
270 public string RenameTo {
275 CheckRequestStarted ();
276 if (value == null || value.Length == 0)
277 throw new ArgumentException ("RenameTo value can't be null or empty", "RenameTo");
283 public override Uri RequestUri {
289 public ServicePoint ServicePoint {
291 return GetServicePoint ();
295 public bool UsePassive {
300 CheckRequestStarted ();
306 public override bool UseDefaultCredentials
309 throw GetMustImplement ();
312 throw GetMustImplement ();
316 public bool UseBinary {
320 CheckRequestStarted ();
325 public override int Timeout {
330 CheckRequestStarted ();
333 throw new ArgumentOutOfRangeException ();
341 return binary ? "I" : "A";
356 requestState = value;
361 public override void Abort () {
363 if (State == RequestState.TransferInProgress) {
364 /*FtpStatus status = */
365 SendCommand (false, AbortCommand);
368 if (!InFinalState ()) {
369 State = RequestState.Aborted;
370 ftpResponse = new FtpWebResponse (this, requestUri, method, FtpStatusCode.FileActionAborted, "Aborted by request");
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");
382 asyncResult = new FtpAsyncResult (callback, state);
386 asyncResult.SetCompleted (true, ftpResponse);
388 if (State == RequestState.Before)
389 State = RequestState.Scheduled;
391 Thread thread = new Thread (ProcessRequest);
399 public override WebResponse EndGetResponse (IAsyncResult asyncResult) {
400 if (asyncResult == null)
401 throw new ArgumentNullException ("AsyncResult cannot be null!");
403 if (!(asyncResult is FtpAsyncResult) || asyncResult != this.asyncResult)
404 throw new ArgumentException ("AsyncResult is from another request!");
406 FtpAsyncResult asyncFtpResult = (FtpAsyncResult) asyncResult;
407 if (!asyncFtpResult.WaitUntilComplete (timeout, false)) {
409 throw new WebException ("Transfer timed out.", WebExceptionStatus.Timeout);
416 if (asyncFtpResult.GotException)
417 throw asyncFtpResult.Exception;
419 return asyncFtpResult.Response;
422 public override WebResponse GetResponse () {
423 IAsyncResult asyncResult = BeginGetResponse (null, null);
424 return EndGetResponse (asyncResult);
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 ();
435 if (State != RequestState.Before)
436 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
438 State = RequestState.Scheduled;
441 asyncResult = new FtpAsyncResult (callback, state);
442 Thread thread = new Thread (ProcessRequest);
448 public override Stream EndGetRequestStream (IAsyncResult asyncResult) {
449 if (asyncResult == null)
450 throw new ArgumentNullException ("asyncResult");
452 if (!(asyncResult is FtpAsyncResult))
453 throw new ArgumentException ("asyncResult");
455 if (State == RequestState.Aborted) {
456 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
459 if (asyncResult != this.asyncResult)
460 throw new ArgumentException ("AsyncResult is from another request!");
462 FtpAsyncResult res = (FtpAsyncResult) asyncResult;
464 if (!res.WaitUntilComplete (timeout, false)) {
466 throw new WebException ("Request timed out");
469 if (res.GotException)
475 public override Stream GetRequestStream () {
476 IAsyncResult asyncResult = BeginGetRequestStream (null, null);
477 return EndGetRequestStream (asyncResult);
480 ServicePoint GetServicePoint ()
482 if (servicePoint == null)
483 servicePoint = ServicePointManager.FindServicePoint (requestUri, proxy);
488 // Probably move some code of command connection here
492 hostEntry = GetServicePoint ().HostEntry;
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);
501 void ProcessRequest () {
503 if (State == RequestState.Scheduled) {
504 ftpResponse = new FtpWebResponse (this, requestUri, method, keepAlive);
508 //State = RequestState.Finished;
509 //finalResponse = ftpResponse;
510 asyncResult.SetCompleted (false, ftpResponse);
512 catch (Exception e) {
513 if (!GetServicePoint ().UsesProxy)
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 UriBuilder initialBuilder = new UriBuilder () {
557 Uri initial = initialBuilder.Uri;
558 result = new Uri (initial, local_path).LocalPath;
561 int last = result.LastIndexOf ('/');
565 return result.Substring (0, last + 1);
568 void CWDAndSetFileName (Uri uri)
570 string remote_folder = GetRemoteFolderPath (uri);
572 if (remote_folder != null) {
573 status = SendCommand (ChangeDir, remote_folder);
574 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
575 throw CreateExceptionFromResponse (status);
577 int last = uri.LocalPath.LastIndexOf ('/');
579 file_name = Uri.UnescapeDataString (uri.LocalPath.Substring (last + 1));
584 void ProcessMethod ()
586 ServicePoint sp = GetServicePoint ();
588 if (method != WebRequestMethods.Ftp.DownloadFile)
589 throw new NotSupportedException ("FTP+proxy only supports RETR");
591 HttpWebRequest req = (HttpWebRequest) WebRequest.Create (proxy.GetProxy (requestUri));
592 req.Address = requestUri;
593 requestState = RequestState.Finished;
594 WebResponse response = req.GetResponse ();
595 ftpResponse.Stream = new FtpDataStream (this, response.GetResponseStream (), true);
596 ftpResponse.StatusCode = FtpStatusCode.CommandOK;
599 State = RequestState.Connecting;
603 OpenControlConnection ();
604 CWDAndSetFileName (requestUri);
608 // Open data connection and receive data
609 case WebRequestMethods.Ftp.DownloadFile:
610 case WebRequestMethods.Ftp.ListDirectory:
611 case WebRequestMethods.Ftp.ListDirectoryDetails:
614 // Open data connection and send data
615 case WebRequestMethods.Ftp.AppendFile:
616 case WebRequestMethods.Ftp.UploadFile:
617 case WebRequestMethods.Ftp.UploadFileWithUniqueName:
620 // Get info from control connection
621 case WebRequestMethods.Ftp.GetFileSize:
622 case WebRequestMethods.Ftp.GetDateTimestamp:
623 case WebRequestMethods.Ftp.PrintWorkingDirectory:
624 case WebRequestMethods.Ftp.MakeDirectory:
625 case WebRequestMethods.Ftp.Rename:
626 case WebRequestMethods.Ftp.DeleteFile:
627 ProcessSimpleMethod ();
629 default: // What to do here?
630 throw new Exception (String.Format ("Support for command {0} not implemented yet", method));
636 private void CloseControlConnection () {
637 if (controlStream != null) {
638 SendCommand (QuitCommand);
639 controlStream.Close ();
640 controlStream = null;
644 internal void CloseDataConnection () {
645 if(origDataStream != null) {
646 origDataStream.Close ();
647 origDataStream = null;
651 private void CloseConnection () {
652 CloseControlConnection ();
653 CloseDataConnection ();
656 void ProcessSimpleMethod ()
658 State = RequestState.TransferInProgress;
662 if (method == WebRequestMethods.Ftp.PrintWorkingDirectory)
665 if (method == WebRequestMethods.Ftp.Rename)
666 method = RenameFromCommand;
668 status = SendCommand (method, file_name);
670 ftpResponse.Stream = Stream.Null;
672 string desc = status.StatusDescription;
675 case WebRequestMethods.Ftp.GetFileSize: {
676 if (status.StatusCode != FtpStatusCode.FileStatus)
677 throw CreateExceptionFromResponse (status);
681 for (i = 4, len = 0; i < desc.Length && Char.IsDigit (desc [i]); i++, len++)
685 throw new WebException ("Bad format for server response in " + method);
687 if (!Int64.TryParse (desc.Substring (4, len), out size))
688 throw new WebException ("Bad format for server response in " + method);
690 ftpResponse.contentLength = size;
693 case WebRequestMethods.Ftp.GetDateTimestamp:
694 if (status.StatusCode != FtpStatusCode.FileStatus)
695 throw CreateExceptionFromResponse (status);
696 ftpResponse.LastModified = DateTime.ParseExact (desc.Substring (4), "yyyyMMddHHmmss", null);
698 case WebRequestMethods.Ftp.MakeDirectory:
699 if (status.StatusCode != FtpStatusCode.PathnameCreated)
700 throw CreateExceptionFromResponse (status);
703 method = WebRequestMethods.Ftp.PrintWorkingDirectory;
705 if (status.StatusCode != FtpStatusCode.FileActionOK)
706 throw CreateExceptionFromResponse (status);
708 status = SendCommand (method);
710 if (status.StatusCode != FtpStatusCode.PathnameCreated)
711 throw CreateExceptionFromResponse (status);
713 case RenameFromCommand:
714 method = WebRequestMethods.Ftp.Rename;
715 if (status.StatusCode != FtpStatusCode.FileCommandPending)
716 throw CreateExceptionFromResponse (status);
717 // Pass an empty string if RenameTo wasn't specified
718 status = SendCommand (RenameToCommand, renameTo != null ? renameTo : String.Empty);
719 if (status.StatusCode != FtpStatusCode.FileActionOK)
720 throw CreateExceptionFromResponse (status);
722 case WebRequestMethods.Ftp.DeleteFile:
723 if (status.StatusCode != FtpStatusCode.FileActionOK) {
724 throw CreateExceptionFromResponse (status);
729 State = RequestState.Finished;
734 State = RequestState.OpeningData;
736 OpenDataConnection ();
738 State = RequestState.TransferInProgress;
739 requestStream = new FtpDataStream (this, dataStream, false);
740 asyncResult.Stream = requestStream;
745 State = RequestState.OpeningData;
747 OpenDataConnection ();
749 State = RequestState.TransferInProgress;
750 ftpResponse.Stream = new FtpDataStream (this, dataStream, true);
753 void CheckRequestStarted ()
755 if (State != RequestState.Before)
756 throw new InvalidOperationException ("There is a request currently in progress");
759 void OpenControlConnection ()
761 Exception exception = null;
763 foreach (IPAddress address in hostEntry.AddressList) {
764 sock = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
766 IPEndPoint remote = new IPEndPoint (address, requestUri.Port);
768 if (!ServicePoint.CallEndPointDelegate (sock, remote)) {
773 sock.Connect (remote);
774 localEndPoint = (IPEndPoint) sock.LocalEndPoint;
776 } catch (SocketException exc) {
784 // Couldn't connect to any address
786 throw new WebException ("Unable to connect to remote server", exception,
787 WebExceptionStatus.UnknownError, ftpResponse);
789 controlStream = new NetworkStream (sock);
790 controlReader = new StreamReader (controlStream, Encoding.ASCII);
792 State = RequestState.Authenticating;
795 FtpStatus status = SendCommand ("OPTS", "utf8", "on");
796 // ignore status for OPTS
797 status = SendCommand (WebRequestMethods.Ftp.PrintWorkingDirectory);
798 initial_path = GetInitialPath (status);
801 static string GetInitialPath (FtpStatus status)
803 int s = (int) status.StatusCode;
804 if (s < 200 || s > 300 || status.StatusDescription.Length <= 4)
805 throw new WebException ("Error getting current directory: " + status.StatusDescription, null,
806 WebExceptionStatus.UnknownError, null);
808 string msg = status.StatusDescription.Substring (4);
809 if (msg [0] == '"') {
810 int next_quote = msg.IndexOf ('\"', 1);
811 if (next_quote == -1)
812 throw new WebException ("Error getting current directory: PWD -> " + status.StatusDescription, null,
813 WebExceptionStatus.UnknownError, null);
815 msg = msg.Substring (1, next_quote - 1);
818 if (!msg.EndsWith ("/"))
823 // Probably we could do better having here a regex
824 Socket SetupPassiveConnection (string statusDescription)
826 // Current response string
827 string response = statusDescription;
828 if (response.Length < 4)
829 throw new WebException ("Cannot open passive data connection");
831 // Look for first digit after code
833 for (i = 3; i < response.Length && !Char.IsDigit (response [i]); i++)
835 if (i >= response.Length)
836 throw new WebException ("Cannot open passive data connection");
839 string [] digits = response.Substring (i).Split (new char [] {','}, 6);
840 if (digits.Length != 6)
841 throw new WebException ("Cannot open passive data connection");
843 // Clean non-digits at the end of last element
845 for (j = digits [5].Length - 1; j >= 0 && !Char.IsDigit (digits [5][j]); j--)
848 throw new WebException ("Cannot open passive data connection");
850 digits [5] = digits [5].Substring (0, j + 1);
854 ip = IPAddress.Parse (String.Join (".", digits, 0, 4));
855 } catch (FormatException) {
856 throw new WebException ("Cannot open passive data connection");
861 if (!Int32.TryParse (digits [4], out p1) || !Int32.TryParse (digits [5], out p2))
862 throw new WebException ("Cannot open passive data connection");
864 port = (p1 << 8) + p2; // p1 * 256 + p2
865 //port = p1 * 256 + p2;
866 if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
867 throw new WebException ("Cannot open passive data connection");
869 IPEndPoint ep = new IPEndPoint (ip, port);
870 Socket sock = new Socket (ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
873 } catch (SocketException) {
875 throw new WebException ("Cannot open passive data connection");
881 Exception CreateExceptionFromResponse (FtpStatus status)
883 FtpWebResponse ftpResponse = new FtpWebResponse (this, requestUri, method, status);
885 WebException exc = new WebException ("Server returned an error: " + status.StatusDescription,
886 null, WebExceptionStatus.ProtocolError, ftpResponse);
890 // Here we could also get a server error, so be cautious
891 internal void SetTransferCompleted ()
896 State = RequestState.Finished;
897 FtpStatus status = GetResponseStatus ();
898 ftpResponse.UpdateStatus (status);
903 internal void OperationCompleted ()
909 void SetCompleteWithError (Exception exc)
911 if (asyncResult != null) {
912 asyncResult.SetCompleted (false, exc);
916 Socket InitDataConnection ()
921 status = SendCommand (PassiveCommand);
922 if (status.StatusCode != FtpStatusCode.EnteringPassive) {
923 throw CreateExceptionFromResponse (status);
926 return SetupPassiveConnection (status.StatusDescription);
929 // Open a socket to listen the server's connection
930 Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
932 sock.Bind (new IPEndPoint (localEndPoint.Address, 0));
933 sock.Listen (1); // We only expect a connection from server
935 } catch (SocketException e) {
938 throw new WebException ("Couldn't open listening socket on client", e);
941 IPEndPoint ep = (IPEndPoint) sock.LocalEndPoint;
942 string ipString = ep.Address.ToString ().Replace ('.', ',');
943 int h1 = ep.Port >> 8; // ep.Port / 256
944 int h2 = ep.Port % 256;
946 string portParam = ipString + "," + h1 + "," + h2;
947 status = SendCommand (PortCommand, portParam);
949 if (status.StatusCode != FtpStatusCode.CommandOK) {
951 throw (CreateExceptionFromResponse (status));
957 void OpenDataConnection ()
961 Socket s = InitDataConnection ();
963 // Handle content offset
965 status = SendCommand (RestCommand, offset.ToString ());
966 if (status.StatusCode != FtpStatusCode.FileCommandPending)
967 throw CreateExceptionFromResponse (status);
970 if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails &&
971 method != WebRequestMethods.Ftp.UploadFileWithUniqueName) {
972 status = SendCommand (method, file_name);
974 status = SendCommand (method);
977 if (status.StatusCode != FtpStatusCode.OpeningData && status.StatusCode != FtpStatusCode.DataAlreadyOpen)
978 throw CreateExceptionFromResponse (status);
981 origDataStream = new NetworkStream (s, true);
982 dataStream = origDataStream;
984 ChangeToSSLSocket (ref dataStream);
988 // Active connection (use Socket.Blocking to true)
989 Socket incoming = null;
991 incoming = s.Accept ();
993 catch (SocketException) {
995 if (incoming != null)
998 throw new ProtocolViolationException ("Server commited a protocol violation.");
1002 origDataStream = new NetworkStream (incoming, true);
1003 dataStream = origDataStream;
1005 ChangeToSSLSocket (ref dataStream);
1008 ftpResponse.UpdateStatus (status);
1011 void Authenticate ()
1013 string username = null;
1014 string password = null;
1015 string domain = null;
1017 if (credentials != null) {
1018 username = credentials.UserName;
1019 password = credentials.Password;
1020 domain = credentials.Domain;
1023 if (username == null)
1024 username = "anonymous";
1025 if (password == null)
1026 password = "@anonymous";
1027 if (!string.IsNullOrEmpty (domain))
1028 username = domain + '\\' + username;
1030 // Connect to server and get banner message
1031 FtpStatus status = GetResponseStatus ();
1032 ftpResponse.BannerMessage = status.StatusDescription;
1035 InitiateSecureConnection (ref controlStream);
1036 controlReader = new StreamReader (controlStream, Encoding.ASCII);
1037 status = SendCommand ("PBSZ", "0");
1038 int st = (int) status.StatusCode;
1039 if (st < 200 || st >= 300)
1040 throw CreateExceptionFromResponse (status);
1041 // TODO: what if "PROT P" is denied by the server? What does MS do?
1042 status = SendCommand ("PROT", "P");
1043 st = (int) status.StatusCode;
1044 if (st < 200 || st >= 300)
1045 throw CreateExceptionFromResponse (status);
1047 status = new FtpStatus (FtpStatusCode.SendUserCommand, "");
1050 if (status.StatusCode != FtpStatusCode.SendUserCommand)
1051 throw CreateExceptionFromResponse (status);
1053 status = SendCommand (UserCommand, username);
1055 switch (status.StatusCode) {
1056 case FtpStatusCode.SendPasswordCommand:
1057 status = SendCommand (PasswordCommand, password);
1058 if (status.StatusCode != FtpStatusCode.LoggedInProceed)
1059 throw CreateExceptionFromResponse (status);
1061 case FtpStatusCode.LoggedInProceed:
1064 throw CreateExceptionFromResponse (status);
1067 ftpResponse.WelcomeMessage = status.StatusDescription;
1068 ftpResponse.UpdateStatus (status);
1071 FtpStatus SendCommand (string command, params string [] parameters) {
1072 return SendCommand (true, command, parameters);
1075 FtpStatus SendCommand (bool waitResponse, string command, params string [] parameters)
1078 string commandString = command;
1079 if (parameters.Length > 0)
1080 commandString += " " + String.Join (" ", parameters);
1082 commandString += EOL;
1083 cmd = Encoding.ASCII.GetBytes (commandString);
1085 controlStream.Write (cmd, 0, cmd.Length);
1086 } catch (IOException) {
1087 //controlStream.Close ();
1088 return new FtpStatus(FtpStatusCode.ServiceNotAvailable, "Write failed");
1094 FtpStatus result = GetResponseStatus ();
1095 if (ftpResponse != null)
1096 ftpResponse.UpdateStatus (result);
1100 internal static FtpStatus ServiceNotAvailable ()
1102 return new FtpStatus (FtpStatusCode.ServiceNotAvailable, Locale.GetText ("Invalid response from server"));
1105 internal FtpStatus GetResponseStatus ()
1108 string response = null;
1111 response = controlReader.ReadLine ();
1112 } catch (IOException) {
1115 if (response == null || response.Length < 3)
1116 return ServiceNotAvailable ();
1119 if (!Int32.TryParse (response.Substring (0, 3), out code))
1120 return ServiceNotAvailable ();
1122 if (response.Length > 3 && response [3] == '-'){
1124 string find = code.ToString() + ' ';
1128 line = controlReader.ReadLine();
1129 } catch (IOException) {
1132 return ServiceNotAvailable ();
1134 response += Environment.NewLine + line;
1136 if (line.StartsWith(find, StringComparison.Ordinal))
1140 return new FtpStatus ((FtpStatusCode) code, response);
1144 private void InitiateSecureConnection (ref Stream stream) {
1145 FtpStatus status = SendCommand (AuthCommand, "TLS");
1146 if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession)
1147 throw CreateExceptionFromResponse (status);
1149 ChangeToSSLSocket (ref stream);
1153 RemoteCertificateValidationCallback callback = delegate (object sender,
1154 X509Certificate certificate,
1156 SslPolicyErrors sslPolicyErrors) {
1157 // honor any exciting callback defined on ServicePointManager
1158 if (ServicePointManager.ServerCertificateValidationCallback != null)
1159 return ServicePointManager.ServerCertificateValidationCallback (sender, certificate, chain, sslPolicyErrors);
1160 // otherwise provide our own
1161 if (sslPolicyErrors != SslPolicyErrors.None)
1162 throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors);
1167 internal bool ChangeToSSLSocket (ref Stream stream) {
1169 SslStream sslStream = new SslStream (stream, true, callback, null);
1170 //sslStream.AuthenticateAsClient (Host, this.ClientCertificates, SslProtocols.Default, false);
1171 //TODO: client certificates
1172 sslStream.AuthenticateAsClient (requestUri.Host, null, SslProtocols.Default, false);
1176 throw new NotImplementedException ();
1180 bool InFinalState () {
1181 return (State == RequestState.Aborted || State == RequestState.Error || State == RequestState.Finished);
1184 bool InProgress () {
1185 return (State != RequestState.Before && !InFinalState ());
1188 internal void CheckIfAborted () {
1189 if (State == RequestState.Aborted)
1190 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
1193 void CheckFinalState () {
1194 if (InFinalState ())
1195 throw new InvalidOperationException ("Cannot change final state");