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;
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 ();
254 public int ReadWriteTimeout {
259 CheckRequestStarted ();
262 throw new ArgumentOutOfRangeException ();
268 public string RenameTo {
273 CheckRequestStarted ();
274 if (value == null || value.Length == 0)
275 throw new ArgumentException ("RenameTo value can't be null or empty", "RenameTo");
281 public override Uri RequestUri {
287 public ServicePoint ServicePoint {
289 return GetServicePoint ();
293 public bool UsePassive {
298 CheckRequestStarted ();
304 public override bool UseDefaultCredentials
307 throw GetMustImplement ();
310 throw GetMustImplement ();
314 public bool UseBinary {
318 CheckRequestStarted ();
323 public override int Timeout {
328 CheckRequestStarted ();
331 throw new ArgumentOutOfRangeException ();
339 return binary ? "I" : "A";
354 requestState = value;
359 public override void Abort () {
361 if (State == RequestState.TransferInProgress) {
362 /*FtpStatus status = */
363 SendCommand (false, AbortCommand);
366 if (!InFinalState ()) {
367 State = RequestState.Aborted;
368 ftpResponse = new FtpWebResponse (this, requestUri, method, FtpStatusCode.FileActionAborted, "Aborted by request");
373 public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state) {
374 if (asyncResult != null && !asyncResult.IsCompleted) {
375 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
380 asyncResult = new FtpAsyncResult (callback, state);
384 asyncResult.SetCompleted (true, ftpResponse);
386 if (State == RequestState.Before)
387 State = RequestState.Scheduled;
389 Thread thread = new Thread (ProcessRequest);
397 public override WebResponse EndGetResponse (IAsyncResult asyncResult) {
398 if (asyncResult == null)
399 throw new ArgumentNullException ("AsyncResult cannot be null!");
401 if (!(asyncResult is FtpAsyncResult) || asyncResult != this.asyncResult)
402 throw new ArgumentException ("AsyncResult is from another request!");
404 FtpAsyncResult asyncFtpResult = (FtpAsyncResult) asyncResult;
405 if (!asyncFtpResult.WaitUntilComplete (timeout, false)) {
407 throw new WebException ("Transfer timed out.", WebExceptionStatus.Timeout);
414 if (asyncFtpResult.GotException)
415 throw asyncFtpResult.Exception;
417 return asyncFtpResult.Response;
420 public override WebResponse GetResponse () {
421 IAsyncResult asyncResult = BeginGetResponse (null, null);
422 return EndGetResponse (asyncResult);
425 public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state) {
426 if (method != WebRequestMethods.Ftp.UploadFile && method != WebRequestMethods.Ftp.UploadFileWithUniqueName &&
427 method != WebRequestMethods.Ftp.AppendFile)
428 throw new ProtocolViolationException ();
433 if (State != RequestState.Before)
434 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
436 State = RequestState.Scheduled;
439 asyncResult = new FtpAsyncResult (callback, state);
440 Thread thread = new Thread (ProcessRequest);
446 public override Stream EndGetRequestStream (IAsyncResult asyncResult) {
447 if (asyncResult == null)
448 throw new ArgumentNullException ("asyncResult");
450 if (!(asyncResult is FtpAsyncResult))
451 throw new ArgumentException ("asyncResult");
453 if (State == RequestState.Aborted) {
454 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
457 if (asyncResult != this.asyncResult)
458 throw new ArgumentException ("AsyncResult is from another request!");
460 FtpAsyncResult res = (FtpAsyncResult) asyncResult;
462 if (!res.WaitUntilComplete (timeout, false)) {
464 throw new WebException ("Request timed out");
467 if (res.GotException)
473 public override Stream GetRequestStream () {
474 IAsyncResult asyncResult = BeginGetRequestStream (null, null);
475 return EndGetRequestStream (asyncResult);
478 ServicePoint GetServicePoint ()
480 if (servicePoint == null)
481 servicePoint = ServicePointManager.FindServicePoint (requestUri, proxy);
486 // Probably move some code of command connection here
490 hostEntry = GetServicePoint ().HostEntry;
492 if (hostEntry == null) {
493 ftpResponse.UpdateStatus (new FtpStatus(FtpStatusCode.ActionAbortedLocalProcessingError, "Cannot resolve server name"));
494 throw new WebException ("The remote server name could not be resolved: " + requestUri,
495 null, WebExceptionStatus.NameResolutionFailure, ftpResponse);
499 void ProcessRequest () {
501 if (State == RequestState.Scheduled) {
502 ftpResponse = new FtpWebResponse (this, requestUri, method, keepAlive);
506 //State = RequestState.Finished;
507 //finalResponse = ftpResponse;
508 asyncResult.SetCompleted (false, ftpResponse);
510 catch (Exception e) {
511 if (!GetServicePoint ().UsesProxy)
512 State = RequestState.Error;
513 SetCompleteWithError (e);
518 FtpStatus status = GetResponseStatus ();
520 ftpResponse.UpdateStatus (status);
522 if (ftpResponse.IsFinal ()) {
523 State = RequestState.Finished;
527 asyncResult.SetCompleted (false, ftpResponse);
534 FtpStatus status = SendCommand (TypeCommand, DataType);
535 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
536 throw CreateExceptionFromResponse (status);
540 string GetRemoteFolderPath (Uri uri)
543 string local_path = Uri.UnescapeDataString (uri.LocalPath);
544 if (initial_path == null || initial_path == "/") {
547 if (local_path [0] == '/')
548 local_path = local_path.Substring (1);
550 Uri initial = new Uri ("ftp://dummy-host" + initial_path);
551 result = new Uri (initial, local_path).LocalPath;
554 int last = result.LastIndexOf ('/');
558 return result.Substring (0, last + 1);
561 void CWDAndSetFileName (Uri uri)
563 string remote_folder = GetRemoteFolderPath (uri);
565 if (remote_folder != null) {
566 status = SendCommand (ChangeDir, remote_folder);
567 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
568 throw CreateExceptionFromResponse (status);
570 int last = uri.LocalPath.LastIndexOf ('/');
572 file_name = Uri.UnescapeDataString (uri.LocalPath.Substring (last + 1));
577 void ProcessMethod ()
579 ServicePoint sp = GetServicePoint ();
581 if (method != WebRequestMethods.Ftp.DownloadFile)
582 throw new NotSupportedException ("FTP+proxy only supports RETR");
584 HttpWebRequest req = (HttpWebRequest) WebRequest.Create (proxy.GetProxy (requestUri));
585 req.Address = requestUri;
586 requestState = RequestState.Finished;
587 WebResponse response = req.GetResponse ();
588 ftpResponse.Stream = new FtpDataStream (this, response.GetResponseStream (), true);
589 ftpResponse.StatusCode = FtpStatusCode.CommandOK;
592 State = RequestState.Connecting;
596 OpenControlConnection ();
597 CWDAndSetFileName (requestUri);
601 // Open data connection and receive data
602 case WebRequestMethods.Ftp.DownloadFile:
603 case WebRequestMethods.Ftp.ListDirectory:
604 case WebRequestMethods.Ftp.ListDirectoryDetails:
607 // Open data connection and send data
608 case WebRequestMethods.Ftp.AppendFile:
609 case WebRequestMethods.Ftp.UploadFile:
610 case WebRequestMethods.Ftp.UploadFileWithUniqueName:
613 // Get info from control connection
614 case WebRequestMethods.Ftp.GetFileSize:
615 case WebRequestMethods.Ftp.GetDateTimestamp:
616 case WebRequestMethods.Ftp.PrintWorkingDirectory:
617 case WebRequestMethods.Ftp.MakeDirectory:
618 case WebRequestMethods.Ftp.Rename:
619 case WebRequestMethods.Ftp.DeleteFile:
620 ProcessSimpleMethod ();
622 default: // What to do here?
623 throw new Exception (String.Format ("Support for command {0} not implemented yet", method));
629 private void CloseControlConnection () {
630 if (controlStream != null) {
631 SendCommand (QuitCommand);
632 controlStream.Close ();
633 controlStream = null;
637 internal void CloseDataConnection () {
638 if(origDataStream != null) {
639 origDataStream.Close ();
640 origDataStream = null;
644 private void CloseConnection () {
645 CloseControlConnection ();
646 CloseDataConnection ();
649 void ProcessSimpleMethod ()
651 State = RequestState.TransferInProgress;
655 if (method == WebRequestMethods.Ftp.PrintWorkingDirectory)
658 if (method == WebRequestMethods.Ftp.Rename)
659 method = RenameFromCommand;
661 status = SendCommand (method, file_name);
663 ftpResponse.Stream = Stream.Null;
665 string desc = status.StatusDescription;
668 case WebRequestMethods.Ftp.GetFileSize: {
669 if (status.StatusCode != FtpStatusCode.FileStatus)
670 throw CreateExceptionFromResponse (status);
674 for (i = 4, len = 0; i < desc.Length && Char.IsDigit (desc [i]); i++, len++)
678 throw new WebException ("Bad format for server response in " + method);
680 if (!Int64.TryParse (desc.Substring (4, len), out size))
681 throw new WebException ("Bad format for server response in " + method);
683 ftpResponse.contentLength = size;
686 case WebRequestMethods.Ftp.GetDateTimestamp:
687 if (status.StatusCode != FtpStatusCode.FileStatus)
688 throw CreateExceptionFromResponse (status);
689 ftpResponse.LastModified = DateTime.ParseExact (desc.Substring (4), "yyyyMMddHHmmss", null);
691 case WebRequestMethods.Ftp.MakeDirectory:
692 if (status.StatusCode != FtpStatusCode.PathnameCreated)
693 throw CreateExceptionFromResponse (status);
696 method = WebRequestMethods.Ftp.PrintWorkingDirectory;
698 if (status.StatusCode != FtpStatusCode.FileActionOK)
699 throw CreateExceptionFromResponse (status);
701 status = SendCommand (method);
703 if (status.StatusCode != FtpStatusCode.PathnameCreated)
704 throw CreateExceptionFromResponse (status);
706 case RenameFromCommand:
707 method = WebRequestMethods.Ftp.Rename;
708 if (status.StatusCode != FtpStatusCode.FileCommandPending)
709 throw CreateExceptionFromResponse (status);
710 // Pass an empty string if RenameTo wasn't specified
711 status = SendCommand (RenameToCommand, renameTo != null ? renameTo : String.Empty);
712 if (status.StatusCode != FtpStatusCode.FileActionOK)
713 throw CreateExceptionFromResponse (status);
715 case WebRequestMethods.Ftp.DeleteFile:
716 if (status.StatusCode != FtpStatusCode.FileActionOK) {
717 throw CreateExceptionFromResponse (status);
722 State = RequestState.Finished;
727 State = RequestState.OpeningData;
729 OpenDataConnection ();
731 State = RequestState.TransferInProgress;
732 requestStream = new FtpDataStream (this, dataStream, false);
733 asyncResult.Stream = requestStream;
738 State = RequestState.OpeningData;
740 OpenDataConnection ();
742 State = RequestState.TransferInProgress;
743 ftpResponse.Stream = new FtpDataStream (this, dataStream, true);
746 void CheckRequestStarted ()
748 if (State != RequestState.Before)
749 throw new InvalidOperationException ("There is a request currently in progress");
752 void OpenControlConnection ()
754 Exception exception = null;
756 foreach (IPAddress address in hostEntry.AddressList) {
757 sock = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
759 IPEndPoint remote = new IPEndPoint (address, requestUri.Port);
761 if (!ServicePoint.CallEndPointDelegate (sock, remote)) {
766 sock.Connect (remote);
767 localEndPoint = (IPEndPoint) sock.LocalEndPoint;
769 } catch (SocketException exc) {
777 // Couldn't connect to any address
779 throw new WebException ("Unable to connect to remote server", exception,
780 WebExceptionStatus.UnknownError, ftpResponse);
782 controlStream = new NetworkStream (sock);
783 controlReader = new StreamReader (controlStream, Encoding.ASCII);
785 State = RequestState.Authenticating;
788 FtpStatus status = SendCommand ("OPTS", "utf8", "on");
789 // ignore status for OPTS
790 status = SendCommand (WebRequestMethods.Ftp.PrintWorkingDirectory);
791 initial_path = GetInitialPath (status);
794 static string GetInitialPath (FtpStatus status)
796 int s = (int) status.StatusCode;
797 if (s < 200 || s > 300 || status.StatusDescription.Length <= 4)
798 throw new WebException ("Error getting current directory: " + status.StatusDescription, null,
799 WebExceptionStatus.UnknownError, null);
801 string msg = status.StatusDescription.Substring (4);
802 if (msg [0] == '"') {
803 int next_quote = msg.IndexOf ('\"', 1);
804 if (next_quote == -1)
805 throw new WebException ("Error getting current directory: PWD -> " + status.StatusDescription, null,
806 WebExceptionStatus.UnknownError, null);
808 msg = msg.Substring (1, next_quote - 1);
811 if (!msg.EndsWith ("/"))
816 // Probably we could do better having here a regex
817 Socket SetupPassiveConnection (string statusDescription)
819 // Current response string
820 string response = statusDescription;
821 if (response.Length < 4)
822 throw new WebException ("Cannot open passive data connection");
824 // Look for first digit after code
826 for (i = 3; i < response.Length && !Char.IsDigit (response [i]); i++)
828 if (i >= response.Length)
829 throw new WebException ("Cannot open passive data connection");
832 string [] digits = response.Substring (i).Split (new char [] {','}, 6);
833 if (digits.Length != 6)
834 throw new WebException ("Cannot open passive data connection");
836 // Clean non-digits at the end of last element
838 for (j = digits [5].Length - 1; j >= 0 && !Char.IsDigit (digits [5][j]); j--)
841 throw new WebException ("Cannot open passive data connection");
843 digits [5] = digits [5].Substring (0, j + 1);
847 ip = IPAddress.Parse (String.Join (".", digits, 0, 4));
848 } catch (FormatException) {
849 throw new WebException ("Cannot open passive data connection");
854 if (!Int32.TryParse (digits [4], out p1) || !Int32.TryParse (digits [5], out p2))
855 throw new WebException ("Cannot open passive data connection");
857 port = (p1 << 8) + p2; // p1 * 256 + p2
858 //port = p1 * 256 + p2;
859 if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
860 throw new WebException ("Cannot open passive data connection");
862 IPEndPoint ep = new IPEndPoint (ip, port);
863 Socket sock = new Socket (ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
866 } catch (SocketException) {
868 throw new WebException ("Cannot open passive data connection");
874 Exception CreateExceptionFromResponse (FtpStatus status)
876 FtpWebResponse ftpResponse = new FtpWebResponse (this, requestUri, method, status);
878 WebException exc = new WebException ("Server returned an error: " + status.StatusDescription,
879 null, WebExceptionStatus.ProtocolError, ftpResponse);
883 // Here we could also get a server error, so be cautious
884 internal void SetTransferCompleted ()
889 State = RequestState.Finished;
890 FtpStatus status = GetResponseStatus ();
891 ftpResponse.UpdateStatus (status);
896 internal void OperationCompleted ()
902 void SetCompleteWithError (Exception exc)
904 if (asyncResult != null) {
905 asyncResult.SetCompleted (false, exc);
909 Socket InitDataConnection ()
914 status = SendCommand (PassiveCommand);
915 if (status.StatusCode != FtpStatusCode.EnteringPassive) {
916 throw CreateExceptionFromResponse (status);
919 return SetupPassiveConnection (status.StatusDescription);
922 // Open a socket to listen the server's connection
923 Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
925 sock.Bind (new IPEndPoint (localEndPoint.Address, 0));
926 sock.Listen (1); // We only expect a connection from server
928 } catch (SocketException e) {
931 throw new WebException ("Couldn't open listening socket on client", e);
934 IPEndPoint ep = (IPEndPoint) sock.LocalEndPoint;
935 string ipString = ep.Address.ToString ().Replace ('.', ',');
936 int h1 = ep.Port >> 8; // ep.Port / 256
937 int h2 = ep.Port % 256;
939 string portParam = ipString + "," + h1 + "," + h2;
940 status = SendCommand (PortCommand, portParam);
942 if (status.StatusCode != FtpStatusCode.CommandOK) {
944 throw (CreateExceptionFromResponse (status));
950 void OpenDataConnection ()
954 Socket s = InitDataConnection ();
956 // Handle content offset
958 status = SendCommand (RestCommand, offset.ToString ());
959 if (status.StatusCode != FtpStatusCode.FileCommandPending)
960 throw CreateExceptionFromResponse (status);
963 if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails &&
964 method != WebRequestMethods.Ftp.UploadFileWithUniqueName) {
965 status = SendCommand (method, file_name);
967 status = SendCommand (method);
970 if (status.StatusCode != FtpStatusCode.OpeningData && status.StatusCode != FtpStatusCode.DataAlreadyOpen)
971 throw CreateExceptionFromResponse (status);
974 origDataStream = new NetworkStream (s, true);
975 dataStream = origDataStream;
977 ChangeToSSLSocket (ref dataStream);
981 // Active connection (use Socket.Blocking to true)
982 Socket incoming = null;
984 incoming = s.Accept ();
986 catch (SocketException) {
988 if (incoming != null)
991 throw new ProtocolViolationException ("Server commited a protocol violation.");
995 origDataStream = new NetworkStream (incoming, true);
996 dataStream = origDataStream;
998 ChangeToSSLSocket (ref dataStream);
1001 ftpResponse.UpdateStatus (status);
1004 void Authenticate ()
1006 string username = null;
1007 string password = null;
1008 string domain = null;
1010 if (credentials != null) {
1011 username = credentials.UserName;
1012 password = credentials.Password;
1013 domain = credentials.Domain;
1016 if (username == null)
1017 username = "anonymous";
1018 if (password == null)
1019 password = "@anonymous";
1020 if (!string.IsNullOrEmpty (domain))
1021 username = domain + '\\' + username;
1023 // Connect to server and get banner message
1024 FtpStatus status = GetResponseStatus ();
1025 ftpResponse.BannerMessage = status.StatusDescription;
1028 InitiateSecureConnection (ref controlStream);
1029 controlReader = new StreamReader (controlStream, Encoding.ASCII);
1030 status = SendCommand ("PBSZ", "0");
1031 int st = (int) status.StatusCode;
1032 if (st < 200 || st >= 300)
1033 throw CreateExceptionFromResponse (status);
1034 // TODO: what if "PROT P" is denied by the server? What does MS do?
1035 status = SendCommand ("PROT", "P");
1036 st = (int) status.StatusCode;
1037 if (st < 200 || st >= 300)
1038 throw CreateExceptionFromResponse (status);
1040 status = new FtpStatus (FtpStatusCode.SendUserCommand, "");
1043 if (status.StatusCode != FtpStatusCode.SendUserCommand)
1044 throw CreateExceptionFromResponse (status);
1046 status = SendCommand (UserCommand, username);
1048 switch (status.StatusCode) {
1049 case FtpStatusCode.SendPasswordCommand:
1050 status = SendCommand (PasswordCommand, password);
1051 if (status.StatusCode != FtpStatusCode.LoggedInProceed)
1052 throw CreateExceptionFromResponse (status);
1054 case FtpStatusCode.LoggedInProceed:
1057 throw CreateExceptionFromResponse (status);
1060 ftpResponse.WelcomeMessage = status.StatusDescription;
1061 ftpResponse.UpdateStatus (status);
1064 FtpStatus SendCommand (string command, params string [] parameters) {
1065 return SendCommand (true, command, parameters);
1068 FtpStatus SendCommand (bool waitResponse, string command, params string [] parameters)
1071 string commandString = command;
1072 if (parameters.Length > 0)
1073 commandString += " " + String.Join (" ", parameters);
1075 commandString += EOL;
1076 cmd = Encoding.ASCII.GetBytes (commandString);
1078 controlStream.Write (cmd, 0, cmd.Length);
1079 } catch (IOException) {
1080 //controlStream.Close ();
1081 return new FtpStatus(FtpStatusCode.ServiceNotAvailable, "Write failed");
1087 FtpStatus result = GetResponseStatus ();
1088 if (ftpResponse != null)
1089 ftpResponse.UpdateStatus (result);
1093 internal static FtpStatus ServiceNotAvailable ()
1095 return new FtpStatus (FtpStatusCode.ServiceNotAvailable, Locale.GetText ("Invalid response from server"));
1098 internal FtpStatus GetResponseStatus ()
1101 string response = null;
1104 response = controlReader.ReadLine ();
1105 } catch (IOException) {
1108 if (response == null || response.Length < 3)
1109 return ServiceNotAvailable ();
1112 if (!Int32.TryParse (response.Substring (0, 3), out code))
1113 return ServiceNotAvailable ();
1115 if (response.Length > 3 && response [3] == '-'){
1117 string find = code.ToString() + ' ';
1121 line = controlReader.ReadLine();
1122 } catch (IOException) {
1125 return ServiceNotAvailable ();
1127 response += Environment.NewLine + line;
1129 if (line.StartsWith(find, StringComparison.Ordinal))
1133 return new FtpStatus ((FtpStatusCode) code, response);
1137 private void InitiateSecureConnection (ref Stream stream) {
1138 FtpStatus status = SendCommand (AuthCommand, "TLS");
1139 if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession)
1140 throw CreateExceptionFromResponse (status);
1142 ChangeToSSLSocket (ref stream);
1146 RemoteCertificateValidationCallback callback = delegate (object sender,
1147 X509Certificate certificate,
1149 SslPolicyErrors sslPolicyErrors) {
1150 // honor any exciting callback defined on ServicePointManager
1151 if (ServicePointManager.ServerCertificateValidationCallback != null)
1152 return ServicePointManager.ServerCertificateValidationCallback (sender, certificate, chain, sslPolicyErrors);
1153 // otherwise provide our own
1154 if (sslPolicyErrors != SslPolicyErrors.None)
1155 throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors);
1160 internal bool ChangeToSSLSocket (ref Stream stream) {
1162 stream.ChangeToSSLSocket ();
1165 SslStream sslStream = new SslStream (stream, true, callback, null);
1166 //sslStream.AuthenticateAsClient (Host, this.ClientCertificates, SslProtocols.Default, false);
1167 //TODO: client certificates
1168 sslStream.AuthenticateAsClient (requestUri.Host, null, SslProtocols.Default, false);
1172 throw new NotImplementedException ();
1176 bool InFinalState () {
1177 return (State == RequestState.Aborted || State == RequestState.Error || State == RequestState.Finished);
1180 bool InProgress () {
1181 return (State != RequestState.Before && !InFinalState ());
1184 internal void CheckIfAborted () {
1185 if (State == RequestState.Aborted)
1186 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
1189 void CheckFinalState () {
1190 if (InFinalState ())
1191 throw new InvalidOperationException ("Cannot change final state");