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;
29 Stream origDataStream;
32 StreamReader controlReader;
33 NetworkCredential credentials;
34 IPHostEntry hostEntry;
35 IPEndPoint localEndPoint;
38 int rwTimeout = 300000;
41 bool enableSsl = false;
42 bool usePassive = true;
43 bool keepAlive = false;
44 string method = WebRequestMethods.Ftp.DownloadFile;
46 object locker = new object ();
48 RequestState requestState = RequestState.Before;
49 FtpAsyncResult asyncResult;
50 FtpWebResponse ftpResponse;
54 const string ChangeDir = "CWD";
55 const string UserCommand = "USER";
56 const string PasswordCommand = "PASS";
57 const string TypeCommand = "TYPE";
58 const string PassiveCommand = "PASV";
59 const string PortCommand = "PORT";
60 const string AbortCommand = "ABOR";
61 const string AuthCommand = "AUTH";
62 const string RestCommand = "REST";
63 const string RenameFromCommand = "RNFR";
64 const string RenameToCommand = "RNTO";
65 const string QuitCommand = "QUIT";
66 const string EOL = "\r\n"; // Special end of line
82 static readonly string [] supportedCommands = new string [] {
83 WebRequestMethods.Ftp.AppendFile, // APPE
84 WebRequestMethods.Ftp.DeleteFile, // DELE
85 WebRequestMethods.Ftp.ListDirectoryDetails, // LIST
86 WebRequestMethods.Ftp.GetDateTimestamp, // MDTM
87 WebRequestMethods.Ftp.MakeDirectory, // MKD
88 WebRequestMethods.Ftp.ListDirectory, // NLST
89 WebRequestMethods.Ftp.PrintWorkingDirectory, // PWD
90 WebRequestMethods.Ftp.Rename, // RENAME
91 WebRequestMethods.Ftp.DownloadFile, // RETR
92 WebRequestMethods.Ftp.RemoveDirectory, // RMD
93 WebRequestMethods.Ftp.GetFileSize, // SIZE
94 WebRequestMethods.Ftp.UploadFile, // STOR
95 WebRequestMethods.Ftp.UploadFileWithUniqueName // STUR
98 internal FtpWebRequest (Uri uri)
100 this.requestUri = uri;
101 this.proxy = GlobalProxySelection.Select;
104 static Exception GetMustImplement ()
106 return new NotImplementedException ();
110 public X509CertificateCollection ClientCertificates
113 throw GetMustImplement ();
116 throw GetMustImplement ();
121 public override string ConnectionGroupName
124 throw GetMustImplement ();
127 throw GetMustImplement ();
131 public override string ContentType {
133 throw new NotSupportedException ();
136 throw new NotSupportedException ();
140 public override long ContentLength {
149 public long ContentOffset {
154 CheckRequestStarted ();
156 throw new ArgumentOutOfRangeException ();
162 public override ICredentials Credentials {
167 CheckRequestStarted ();
169 throw new ArgumentNullException ();
170 if (!(value is NetworkCredential))
171 throw new ArgumentException ();
173 credentials = value as NetworkCredential;
178 public static new RequestCachePolicy DefaultCachePolicy
181 throw GetMustImplement ();
184 throw GetMustImplement ();
188 public bool EnableSsl {
193 CheckRequestStarted ();
199 public override WebHeaderCollection Headers
202 throw GetMustImplement ();
205 throw GetMustImplement ();
209 [MonoTODO ("We don't support KeepAlive = true")]
210 public bool KeepAlive {
215 CheckRequestStarted ();
220 public override string Method {
225 CheckRequestStarted ();
227 throw new ArgumentNullException ("Method string cannot be null");
229 if (value.Length == 0 || Array.BinarySearch (supportedCommands, value) < 0)
230 throw new ArgumentException ("Method not supported", "value");
236 public override bool PreAuthenticate {
238 throw new NotSupportedException ();
241 throw new NotSupportedException ();
245 public override IWebProxy Proxy {
250 CheckRequestStarted ();
255 public int ReadWriteTimeout {
260 CheckRequestStarted ();
263 throw new ArgumentOutOfRangeException ();
269 public string RenameTo {
274 CheckRequestStarted ();
275 if (value == null || value.Length == 0)
276 throw new ArgumentException ("RenameTo value can't be null or empty", "RenameTo");
282 public override Uri RequestUri {
288 public ServicePoint ServicePoint {
290 return GetServicePoint ();
294 public bool UsePassive {
299 CheckRequestStarted ();
305 public override bool UseDefaultCredentials
308 throw GetMustImplement ();
311 throw GetMustImplement ();
315 public bool UseBinary {
319 CheckRequestStarted ();
324 public override int Timeout {
329 CheckRequestStarted ();
332 throw new ArgumentOutOfRangeException ();
340 return binary ? "I" : "A";
355 requestState = value;
360 public override void Abort () {
362 if (State == RequestState.TransferInProgress) {
363 /*FtpStatus status = */
364 SendCommand (false, AbortCommand);
367 if (!InFinalState ()) {
368 State = RequestState.Aborted;
369 ftpResponse = new FtpWebResponse (this, requestUri, method, FtpStatusCode.FileActionAborted, "Aborted by request");
374 public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state) {
375 if (asyncResult != null && !asyncResult.IsCompleted) {
376 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
381 asyncResult = new FtpAsyncResult (callback, state);
385 asyncResult.SetCompleted (true, ftpResponse);
387 if (State == RequestState.Before)
388 State = RequestState.Scheduled;
390 Thread thread = new Thread (ProcessRequest);
398 public override WebResponse EndGetResponse (IAsyncResult asyncResult) {
399 if (asyncResult == null)
400 throw new ArgumentNullException ("AsyncResult cannot be null!");
402 if (!(asyncResult is FtpAsyncResult) || asyncResult != this.asyncResult)
403 throw new ArgumentException ("AsyncResult is from another request!");
405 FtpAsyncResult asyncFtpResult = (FtpAsyncResult) asyncResult;
406 if (!asyncFtpResult.WaitUntilComplete (timeout, false)) {
408 throw new WebException ("Transfer timed out.", WebExceptionStatus.Timeout);
415 if (asyncFtpResult.GotException)
416 throw asyncFtpResult.Exception;
418 return asyncFtpResult.Response;
421 public override WebResponse GetResponse () {
422 IAsyncResult asyncResult = BeginGetResponse (null, null);
423 return EndGetResponse (asyncResult);
426 public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state) {
427 if (method != WebRequestMethods.Ftp.UploadFile && method != WebRequestMethods.Ftp.UploadFileWithUniqueName &&
428 method != WebRequestMethods.Ftp.AppendFile)
429 throw new ProtocolViolationException ();
434 if (State != RequestState.Before)
435 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
437 State = RequestState.Scheduled;
440 asyncResult = new FtpAsyncResult (callback, state);
441 Thread thread = new Thread (ProcessRequest);
447 public override Stream EndGetRequestStream (IAsyncResult asyncResult) {
448 if (asyncResult == null)
449 throw new ArgumentNullException ("asyncResult");
451 if (!(asyncResult is FtpAsyncResult))
452 throw new ArgumentException ("asyncResult");
454 if (State == RequestState.Aborted) {
455 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
458 if (asyncResult != this.asyncResult)
459 throw new ArgumentException ("AsyncResult is from another request!");
461 FtpAsyncResult res = (FtpAsyncResult) asyncResult;
463 if (!res.WaitUntilComplete (timeout, false)) {
465 throw new WebException ("Request timed out");
468 if (res.GotException)
474 public override Stream GetRequestStream () {
475 IAsyncResult asyncResult = BeginGetRequestStream (null, null);
476 return EndGetRequestStream (asyncResult);
479 ServicePoint GetServicePoint ()
481 if (servicePoint == null)
482 servicePoint = ServicePointManager.FindServicePoint (requestUri, proxy);
487 // Probably move some code of command connection here
491 hostEntry = GetServicePoint ().HostEntry;
493 if (hostEntry == null) {
494 ftpResponse.UpdateStatus (new FtpStatus(FtpStatusCode.ActionAbortedLocalProcessingError, "Cannot resolve server name"));
495 throw new WebException ("The remote server name could not be resolved: " + requestUri,
496 null, WebExceptionStatus.NameResolutionFailure, ftpResponse);
500 void ProcessRequest () {
502 if (State == RequestState.Scheduled) {
503 ftpResponse = new FtpWebResponse (this, requestUri, method, keepAlive);
507 //State = RequestState.Finished;
508 //finalResponse = ftpResponse;
509 asyncResult.SetCompleted (false, ftpResponse);
511 catch (Exception e) {
512 if (!GetServicePoint ().UsesProxy)
513 State = RequestState.Error;
514 SetCompleteWithError (e);
519 FtpStatus status = GetResponseStatus ();
521 ftpResponse.UpdateStatus (status);
523 if (ftpResponse.IsFinal ()) {
524 State = RequestState.Finished;
528 asyncResult.SetCompleted (false, ftpResponse);
535 FtpStatus status = SendCommand (TypeCommand, DataType);
536 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
537 throw CreateExceptionFromResponse (status);
541 string GetRemoteFolderPath (Uri uri)
544 string local_path = Uri.UnescapeDataString (uri.LocalPath);
545 if (initial_path == null || initial_path == "/") {
548 if (local_path [0] == '/')
549 local_path = local_path.Substring (1);
551 Uri initial = new Uri ("ftp://dummy-host" + initial_path);
552 result = new Uri (initial, local_path).LocalPath;
555 int last = result.LastIndexOf ('/');
559 return result.Substring (0, last + 1);
562 void CWDAndSetFileName (Uri uri)
564 string remote_folder = GetRemoteFolderPath (uri);
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);
571 int last = uri.LocalPath.LastIndexOf ('/');
573 file_name = Uri.UnescapeDataString (uri.LocalPath.Substring (last + 1));
578 void ProcessMethod ()
580 ServicePoint sp = GetServicePoint ();
582 if (method != WebRequestMethods.Ftp.DownloadFile)
583 throw new NotSupportedException ("FTP+proxy only supports RETR");
585 HttpWebRequest req = (HttpWebRequest) WebRequest.Create (proxy.GetProxy (requestUri));
586 req.Address = requestUri;
587 requestState = RequestState.Finished;
588 WebResponse response = req.GetResponse ();
589 ftpResponse.Stream = new FtpDataStream (this, response.GetResponseStream (), true);
590 ftpResponse.StatusCode = FtpStatusCode.CommandOK;
593 State = RequestState.Connecting;
597 OpenControlConnection ();
598 CWDAndSetFileName (requestUri);
602 // Open data connection and receive data
603 case WebRequestMethods.Ftp.DownloadFile:
604 case WebRequestMethods.Ftp.ListDirectory:
605 case WebRequestMethods.Ftp.ListDirectoryDetails:
608 // Open data connection and send data
609 case WebRequestMethods.Ftp.AppendFile:
610 case WebRequestMethods.Ftp.UploadFile:
611 case WebRequestMethods.Ftp.UploadFileWithUniqueName:
614 // Get info from control connection
615 case WebRequestMethods.Ftp.GetFileSize:
616 case WebRequestMethods.Ftp.GetDateTimestamp:
617 case WebRequestMethods.Ftp.PrintWorkingDirectory:
618 case WebRequestMethods.Ftp.MakeDirectory:
619 case WebRequestMethods.Ftp.Rename:
620 case WebRequestMethods.Ftp.DeleteFile:
621 ProcessSimpleMethod ();
623 default: // What to do here?
624 throw new Exception (String.Format ("Support for command {0} not implemented yet", method));
630 private void CloseControlConnection () {
631 if (controlStream != null) {
632 SendCommand (QuitCommand);
633 controlStream.Close ();
634 controlStream = null;
638 internal void CloseDataConnection () {
639 if(origDataStream != null) {
640 origDataStream.Close ();
641 origDataStream = null;
645 private void CloseConnection () {
646 CloseControlConnection ();
647 CloseDataConnection ();
650 void ProcessSimpleMethod ()
652 State = RequestState.TransferInProgress;
656 if (method == WebRequestMethods.Ftp.PrintWorkingDirectory)
659 if (method == WebRequestMethods.Ftp.Rename)
660 method = RenameFromCommand;
662 status = SendCommand (method, file_name);
664 ftpResponse.Stream = Stream.Null;
666 string desc = status.StatusDescription;
669 case WebRequestMethods.Ftp.GetFileSize: {
670 if (status.StatusCode != FtpStatusCode.FileStatus)
671 throw CreateExceptionFromResponse (status);
675 for (i = 4, len = 0; i < desc.Length && Char.IsDigit (desc [i]); i++, len++)
679 throw new WebException ("Bad format for server response in " + method);
681 if (!Int64.TryParse (desc.Substring (4, len), out size))
682 throw new WebException ("Bad format for server response in " + method);
684 ftpResponse.contentLength = size;
687 case WebRequestMethods.Ftp.GetDateTimestamp:
688 if (status.StatusCode != FtpStatusCode.FileStatus)
689 throw CreateExceptionFromResponse (status);
690 ftpResponse.LastModified = DateTime.ParseExact (desc.Substring (4), "yyyyMMddHHmmss", null);
692 case WebRequestMethods.Ftp.MakeDirectory:
693 if (status.StatusCode != FtpStatusCode.PathnameCreated)
694 throw CreateExceptionFromResponse (status);
697 method = WebRequestMethods.Ftp.PrintWorkingDirectory;
699 if (status.StatusCode != FtpStatusCode.FileActionOK)
700 throw CreateExceptionFromResponse (status);
702 status = SendCommand (method);
704 if (status.StatusCode != FtpStatusCode.PathnameCreated)
705 throw CreateExceptionFromResponse (status);
707 case RenameFromCommand:
708 method = WebRequestMethods.Ftp.Rename;
709 if (status.StatusCode != FtpStatusCode.FileCommandPending)
710 throw CreateExceptionFromResponse (status);
711 // Pass an empty string if RenameTo wasn't specified
712 status = SendCommand (RenameToCommand, renameTo != null ? renameTo : String.Empty);
713 if (status.StatusCode != FtpStatusCode.FileActionOK)
714 throw CreateExceptionFromResponse (status);
716 case WebRequestMethods.Ftp.DeleteFile:
717 if (status.StatusCode != FtpStatusCode.FileActionOK) {
718 throw CreateExceptionFromResponse (status);
723 State = RequestState.Finished;
728 State = RequestState.OpeningData;
730 OpenDataConnection ();
732 State = RequestState.TransferInProgress;
733 requestStream = new FtpDataStream (this, dataStream, false);
734 asyncResult.Stream = requestStream;
739 State = RequestState.OpeningData;
741 OpenDataConnection ();
743 State = RequestState.TransferInProgress;
744 ftpResponse.Stream = new FtpDataStream (this, dataStream, true);
747 void CheckRequestStarted ()
749 if (State != RequestState.Before)
750 throw new InvalidOperationException ("There is a request currently in progress");
753 void OpenControlConnection ()
755 Exception exception = null;
757 foreach (IPAddress address in hostEntry.AddressList) {
758 sock = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
760 IPEndPoint remote = new IPEndPoint (address, requestUri.Port);
762 if (!ServicePoint.CallEndPointDelegate (sock, remote)) {
767 sock.Connect (remote);
768 localEndPoint = (IPEndPoint) sock.LocalEndPoint;
770 } catch (SocketException exc) {
778 // Couldn't connect to any address
780 throw new WebException ("Unable to connect to remote server", exception,
781 WebExceptionStatus.UnknownError, ftpResponse);
783 controlStream = new NetworkStream (sock);
784 controlReader = new StreamReader (controlStream, Encoding.ASCII);
786 State = RequestState.Authenticating;
789 FtpStatus status = SendCommand ("OPTS", "utf8", "on");
790 // ignore status for OPTS
791 status = SendCommand (WebRequestMethods.Ftp.PrintWorkingDirectory);
792 initial_path = GetInitialPath (status);
795 static string GetInitialPath (FtpStatus status)
797 int s = (int) status.StatusCode;
798 if (s < 200 || s > 300 || status.StatusDescription.Length <= 4)
799 throw new WebException ("Error getting current directory: " + status.StatusDescription, null,
800 WebExceptionStatus.UnknownError, null);
802 string msg = status.StatusDescription.Substring (4);
803 if (msg [0] == '"') {
804 int next_quote = msg.IndexOf ('\"', 1);
805 if (next_quote == -1)
806 throw new WebException ("Error getting current directory: PWD -> " + status.StatusDescription, null,
807 WebExceptionStatus.UnknownError, null);
809 msg = msg.Substring (1, next_quote - 1);
812 if (!msg.EndsWith ("/"))
817 // Probably we could do better having here a regex
818 Socket SetupPassiveConnection (string statusDescription)
820 // Current response string
821 string response = statusDescription;
822 if (response.Length < 4)
823 throw new WebException ("Cannot open passive data connection");
825 // Look for first digit after code
827 for (i = 3; i < response.Length && !Char.IsDigit (response [i]); i++)
829 if (i >= response.Length)
830 throw new WebException ("Cannot open passive data connection");
833 string [] digits = response.Substring (i).Split (new char [] {','}, 6);
834 if (digits.Length != 6)
835 throw new WebException ("Cannot open passive data connection");
837 // Clean non-digits at the end of last element
839 for (j = digits [5].Length - 1; j >= 0 && !Char.IsDigit (digits [5][j]); j--)
842 throw new WebException ("Cannot open passive data connection");
844 digits [5] = digits [5].Substring (0, j + 1);
848 ip = IPAddress.Parse (String.Join (".", digits, 0, 4));
849 } catch (FormatException) {
850 throw new WebException ("Cannot open passive data connection");
855 if (!Int32.TryParse (digits [4], out p1) || !Int32.TryParse (digits [5], out p2))
856 throw new WebException ("Cannot open passive data connection");
858 port = (p1 << 8) + p2; // p1 * 256 + p2
859 //port = p1 * 256 + p2;
860 if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
861 throw new WebException ("Cannot open passive data connection");
863 IPEndPoint ep = new IPEndPoint (ip, port);
864 Socket sock = new Socket (ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
867 } catch (SocketException) {
869 throw new WebException ("Cannot open passive data connection");
875 Exception CreateExceptionFromResponse (FtpStatus status)
877 FtpWebResponse ftpResponse = new FtpWebResponse (this, requestUri, method, status);
879 WebException exc = new WebException ("Server returned an error: " + status.StatusDescription,
880 null, WebExceptionStatus.ProtocolError, ftpResponse);
884 // Here we could also get a server error, so be cautious
885 internal void SetTransferCompleted ()
890 State = RequestState.Finished;
891 FtpStatus status = GetResponseStatus ();
892 ftpResponse.UpdateStatus (status);
897 internal void OperationCompleted ()
903 void SetCompleteWithError (Exception exc)
905 if (asyncResult != null) {
906 asyncResult.SetCompleted (false, exc);
910 Socket InitDataConnection ()
915 status = SendCommand (PassiveCommand);
916 if (status.StatusCode != FtpStatusCode.EnteringPassive) {
917 throw CreateExceptionFromResponse (status);
920 return SetupPassiveConnection (status.StatusDescription);
923 // Open a socket to listen the server's connection
924 Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
926 sock.Bind (new IPEndPoint (localEndPoint.Address, 0));
927 sock.Listen (1); // We only expect a connection from server
929 } catch (SocketException e) {
932 throw new WebException ("Couldn't open listening socket on client", e);
935 IPEndPoint ep = (IPEndPoint) sock.LocalEndPoint;
936 string ipString = ep.Address.ToString ().Replace ('.', ',');
937 int h1 = ep.Port >> 8; // ep.Port / 256
938 int h2 = ep.Port % 256;
940 string portParam = ipString + "," + h1 + "," + h2;
941 status = SendCommand (PortCommand, portParam);
943 if (status.StatusCode != FtpStatusCode.CommandOK) {
945 throw (CreateExceptionFromResponse (status));
951 void OpenDataConnection ()
955 Socket s = InitDataConnection ();
957 // Handle content offset
959 status = SendCommand (RestCommand, offset.ToString ());
960 if (status.StatusCode != FtpStatusCode.FileCommandPending)
961 throw CreateExceptionFromResponse (status);
964 if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails &&
965 method != WebRequestMethods.Ftp.UploadFileWithUniqueName) {
966 status = SendCommand (method, file_name);
968 status = SendCommand (method);
971 if (status.StatusCode != FtpStatusCode.OpeningData && status.StatusCode != FtpStatusCode.DataAlreadyOpen)
972 throw CreateExceptionFromResponse (status);
975 origDataStream = new NetworkStream (s, true);
976 dataStream = origDataStream;
978 ChangeToSSLSocket (ref dataStream);
982 // Active connection (use Socket.Blocking to true)
983 Socket incoming = null;
985 incoming = s.Accept ();
987 catch (SocketException) {
989 if (incoming != null)
992 throw new ProtocolViolationException ("Server commited a protocol violation.");
996 origDataStream = new NetworkStream (s, true);
997 dataStream = origDataStream;
999 ChangeToSSLSocket (ref dataStream);
1002 ftpResponse.UpdateStatus (status);
1005 void Authenticate ()
1007 string username = null;
1008 string password = null;
1009 string domain = null;
1011 if (credentials != null) {
1012 username = credentials.UserName;
1013 password = credentials.Password;
1014 domain = credentials.Domain;
1017 if (username == null)
1018 username = "anonymous";
1019 if (password == null)
1020 password = "@anonymous";
1021 if (!string.IsNullOrEmpty (domain))
1022 username = domain + '\\' + username;
1024 // Connect to server and get banner message
1025 FtpStatus status = GetResponseStatus ();
1026 ftpResponse.BannerMessage = status.StatusDescription;
1029 InitiateSecureConnection (ref controlStream);
1030 controlReader = new StreamReader (controlStream, Encoding.ASCII);
1031 status = SendCommand ("PBSZ", "0");
1032 int st = (int) status.StatusCode;
1033 if (st < 200 || st >= 300)
1034 throw CreateExceptionFromResponse (status);
1035 // TODO: what if "PROT P" is denied by the server? What does MS do?
1036 status = SendCommand ("PROT", "P");
1037 st = (int) status.StatusCode;
1038 if (st < 200 || st >= 300)
1039 throw CreateExceptionFromResponse (status);
1041 status = new FtpStatus (FtpStatusCode.SendUserCommand, "");
1044 if (status.StatusCode != FtpStatusCode.SendUserCommand)
1045 throw CreateExceptionFromResponse (status);
1047 status = SendCommand (UserCommand, username);
1049 switch (status.StatusCode) {
1050 case FtpStatusCode.SendPasswordCommand:
1051 status = SendCommand (PasswordCommand, password);
1052 if (status.StatusCode != FtpStatusCode.LoggedInProceed)
1053 throw CreateExceptionFromResponse (status);
1055 case FtpStatusCode.LoggedInProceed:
1058 throw CreateExceptionFromResponse (status);
1061 ftpResponse.WelcomeMessage = status.StatusDescription;
1062 ftpResponse.UpdateStatus (status);
1065 FtpStatus SendCommand (string command, params string [] parameters) {
1066 return SendCommand (true, command, parameters);
1069 FtpStatus SendCommand (bool waitResponse, string command, params string [] parameters)
1072 string commandString = command;
1073 if (parameters.Length > 0)
1074 commandString += " " + String.Join (" ", parameters);
1076 commandString += EOL;
1077 cmd = Encoding.ASCII.GetBytes (commandString);
1079 controlStream.Write (cmd, 0, cmd.Length);
1080 } catch (IOException) {
1081 //controlStream.Close ();
1082 return new FtpStatus(FtpStatusCode.ServiceNotAvailable, "Write failed");
1088 FtpStatus result = GetResponseStatus ();
1089 if (ftpResponse != null)
1090 ftpResponse.UpdateStatus (result);
1094 internal static FtpStatus ServiceNotAvailable ()
1096 return new FtpStatus (FtpStatusCode.ServiceNotAvailable, Locale.GetText ("Invalid response from server"));
1099 internal FtpStatus GetResponseStatus ()
1102 string response = null;
1105 response = controlReader.ReadLine ();
1106 } catch (IOException) {
1109 if (response == null || response.Length < 3)
1110 return ServiceNotAvailable ();
1113 if (!Int32.TryParse (response.Substring (0, 3), out code))
1114 return ServiceNotAvailable ();
1116 if (response.Length > 3 && response [3] == '-'){
1118 string find = code.ToString() + ' ';
1122 line = controlReader.ReadLine();
1123 } catch (IOException) {
1126 return ServiceNotAvailable ();
1128 response += Environment.NewLine + line;
1130 if (line.StartsWith(find, StringComparison.Ordinal))
1134 return new FtpStatus ((FtpStatusCode) code, response);
1138 private void InitiateSecureConnection (ref Stream stream) {
1139 FtpStatus status = SendCommand (AuthCommand, "TLS");
1140 if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession)
1141 throw CreateExceptionFromResponse (status);
1143 ChangeToSSLSocket (ref stream);
1147 RemoteCertificateValidationCallback callback = delegate (object sender,
1148 X509Certificate certificate,
1150 SslPolicyErrors sslPolicyErrors) {
1151 // honor any exciting callback defined on ServicePointManager
1152 if (ServicePointManager.ServerCertificateValidationCallback != null)
1153 return ServicePointManager.ServerCertificateValidationCallback (sender, certificate, chain, sslPolicyErrors);
1154 // otherwise provide our own
1155 if (sslPolicyErrors != SslPolicyErrors.None)
1156 throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors);
1161 internal bool ChangeToSSLSocket (ref Stream stream) {
1163 stream.ChangeToSSLSocket ();
1166 SslStream sslStream = new SslStream (stream, true, callback, null);
1167 //sslStream.AuthenticateAsClient (Host, this.ClientCertificates, SslProtocols.Default, false);
1168 //TODO: client certificates
1169 sslStream.AuthenticateAsClient (requestUri.Host, null, SslProtocols.Default, false);
1173 throw new NotImplementedException ();
1177 bool InFinalState () {
1178 return (State == RequestState.Aborted || State == RequestState.Error || State == RequestState.Finished);
1181 bool InProgress () {
1182 return (State != RequestState.Before && !InFinalState ());
1185 internal void CheckIfAborted () {
1186 if (State == RequestState.Aborted)
1187 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
1190 void CheckFinalState () {
1191 if (InFinalState ())
1192 throw new InvalidOperationException ("Cannot change final state");