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 Encoding dataEncoding = Encoding.UTF8;
99 internal FtpWebRequest (Uri uri)
101 this.requestUri = uri;
102 this.proxy = GlobalProxySelection.Select;
105 static Exception GetMustImplement ()
107 return new NotImplementedException ();
111 public X509CertificateCollection ClientCertificates
114 throw GetMustImplement ();
117 throw GetMustImplement ();
122 public override string ConnectionGroupName
125 throw GetMustImplement ();
128 throw GetMustImplement ();
132 public override string ContentType {
134 throw new NotSupportedException ();
137 throw new NotSupportedException ();
141 public override long ContentLength {
150 public long ContentOffset {
155 CheckRequestStarted ();
157 throw new ArgumentOutOfRangeException ();
163 public override ICredentials Credentials {
168 CheckRequestStarted ();
170 throw new ArgumentNullException ();
171 if (!(value is NetworkCredential))
172 throw new ArgumentException ();
174 credentials = value as NetworkCredential;
180 public static new RequestCachePolicy DefaultCachePolicy
183 throw GetMustImplement ();
186 throw GetMustImplement ();
191 public bool EnableSsl {
196 CheckRequestStarted ();
202 public override WebHeaderCollection Headers
205 throw GetMustImplement ();
208 throw GetMustImplement ();
212 [MonoTODO ("We don't support KeepAlive = true")]
213 public bool KeepAlive {
218 CheckRequestStarted ();
223 public override string Method {
228 CheckRequestStarted ();
230 throw new ArgumentNullException ("Method string cannot be null");
232 if (value.Length == 0 || Array.BinarySearch (supportedCommands, value) < 0)
233 throw new ArgumentException ("Method not supported", "value");
239 public override bool PreAuthenticate {
241 throw new NotSupportedException ();
244 throw new NotSupportedException ();
248 public override IWebProxy Proxy {
253 CheckRequestStarted ();
258 public int ReadWriteTimeout {
263 CheckRequestStarted ();
266 throw new ArgumentOutOfRangeException ();
272 public string RenameTo {
277 CheckRequestStarted ();
278 if (value == null || value.Length == 0)
279 throw new ArgumentException ("RenameTo value can't be null or empty", "RenameTo");
285 public override Uri RequestUri {
291 public ServicePoint ServicePoint {
293 return GetServicePoint ();
297 public bool UsePassive {
302 CheckRequestStarted ();
308 public override bool UseDefaultCredentials
311 throw GetMustImplement ();
314 throw GetMustImplement ();
318 public bool UseBinary {
322 CheckRequestStarted ();
327 public override int Timeout {
332 CheckRequestStarted ();
335 throw new ArgumentOutOfRangeException ();
343 return binary ? "I" : "A";
358 requestState = value;
363 public override void Abort () {
365 if (State == RequestState.TransferInProgress) {
366 /*FtpStatus status = */
367 SendCommand (false, AbortCommand);
370 if (!InFinalState ()) {
371 State = RequestState.Aborted;
372 ftpResponse = new FtpWebResponse (this, requestUri, method, FtpStatusCode.FileActionAborted, "Aborted by request");
377 public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state) {
378 if (asyncResult != null && !asyncResult.IsCompleted) {
379 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
384 asyncResult = new FtpAsyncResult (callback, state);
388 asyncResult.SetCompleted (true, ftpResponse);
390 if (State == RequestState.Before)
391 State = RequestState.Scheduled;
393 Thread thread = new Thread (ProcessRequest);
401 public override WebResponse EndGetResponse (IAsyncResult asyncResult) {
402 if (asyncResult == null)
403 throw new ArgumentNullException ("AsyncResult cannot be null!");
405 if (!(asyncResult is FtpAsyncResult) || asyncResult != this.asyncResult)
406 throw new ArgumentException ("AsyncResult is from another request!");
408 FtpAsyncResult asyncFtpResult = (FtpAsyncResult) asyncResult;
409 if (!asyncFtpResult.WaitUntilComplete (timeout, false)) {
411 throw new WebException ("Transfer timed out.", WebExceptionStatus.Timeout);
418 if (asyncFtpResult.GotException)
419 throw asyncFtpResult.Exception;
421 return asyncFtpResult.Response;
424 public override WebResponse GetResponse () {
425 IAsyncResult asyncResult = BeginGetResponse (null, null);
426 return EndGetResponse (asyncResult);
429 public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state) {
430 if (method != WebRequestMethods.Ftp.UploadFile && method != WebRequestMethods.Ftp.UploadFileWithUniqueName &&
431 method != WebRequestMethods.Ftp.AppendFile)
432 throw new ProtocolViolationException ();
437 if (State != RequestState.Before)
438 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
440 State = RequestState.Scheduled;
443 asyncResult = new FtpAsyncResult (callback, state);
444 Thread thread = new Thread (ProcessRequest);
450 public override Stream EndGetRequestStream (IAsyncResult asyncResult) {
451 if (asyncResult == null)
452 throw new ArgumentNullException ("asyncResult");
454 if (!(asyncResult is FtpAsyncResult))
455 throw new ArgumentException ("asyncResult");
457 if (State == RequestState.Aborted) {
458 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
461 if (asyncResult != this.asyncResult)
462 throw new ArgumentException ("AsyncResult is from another request!");
464 FtpAsyncResult res = (FtpAsyncResult) asyncResult;
466 if (!res.WaitUntilComplete (timeout, false)) {
468 throw new WebException ("Request timed out");
471 if (res.GotException)
477 public override Stream GetRequestStream () {
478 IAsyncResult asyncResult = BeginGetRequestStream (null, null);
479 return EndGetRequestStream (asyncResult);
482 ServicePoint GetServicePoint ()
484 if (servicePoint == null)
485 servicePoint = ServicePointManager.FindServicePoint (requestUri, proxy);
490 // Probably move some code of command connection here
494 hostEntry = GetServicePoint ().HostEntry;
496 if (hostEntry == null) {
497 ftpResponse.UpdateStatus (new FtpStatus(FtpStatusCode.ActionAbortedLocalProcessingError, "Cannot resolve server name"));
498 throw new WebException ("The remote server name could not be resolved: " + requestUri,
499 null, WebExceptionStatus.NameResolutionFailure, ftpResponse);
503 void ProcessRequest () {
505 if (State == RequestState.Scheduled) {
506 ftpResponse = new FtpWebResponse (this, requestUri, method, keepAlive);
510 //State = RequestState.Finished;
511 //finalResponse = ftpResponse;
512 asyncResult.SetCompleted (false, ftpResponse);
514 catch (Exception e) {
515 if (!GetServicePoint ().UsesProxy)
516 State = RequestState.Error;
517 SetCompleteWithError (e);
522 FtpStatus status = GetResponseStatus ();
524 ftpResponse.UpdateStatus (status);
526 if (ftpResponse.IsFinal ()) {
527 State = RequestState.Finished;
531 asyncResult.SetCompleted (false, ftpResponse);
538 FtpStatus status = SendCommand (TypeCommand, DataType);
539 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
540 throw CreateExceptionFromResponse (status);
544 string GetRemoteFolderPath (Uri uri)
547 string local_path = Uri.UnescapeDataString (uri.LocalPath);
548 if (initial_path == null || initial_path == "/") {
551 if (local_path [0] == '/')
552 local_path = local_path.Substring (1);
554 UriBuilder initialBuilder = new UriBuilder () {
559 Uri initial = initialBuilder.Uri;
560 result = new Uri (initial, local_path).LocalPath;
563 int last = result.LastIndexOf ('/');
567 return result.Substring (0, last + 1);
570 void CWDAndSetFileName (Uri uri)
572 string remote_folder = GetRemoteFolderPath (uri);
574 if (remote_folder != null) {
575 status = SendCommand (ChangeDir, remote_folder);
576 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
577 throw CreateExceptionFromResponse (status);
579 int last = uri.LocalPath.LastIndexOf ('/');
581 file_name = Uri.UnescapeDataString (uri.LocalPath.Substring (last + 1));
586 void ProcessMethod ()
588 ServicePoint sp = GetServicePoint ();
590 if (method != WebRequestMethods.Ftp.DownloadFile)
591 throw new NotSupportedException ("FTP+proxy only supports RETR");
593 HttpWebRequest req = (HttpWebRequest) WebRequest.Create (proxy.GetProxy (requestUri));
594 req.Address = requestUri;
595 requestState = RequestState.Finished;
596 WebResponse response = req.GetResponse ();
597 ftpResponse.Stream = new FtpDataStream (this, response.GetResponseStream (), true);
598 ftpResponse.StatusCode = FtpStatusCode.CommandOK;
601 State = RequestState.Connecting;
605 OpenControlConnection ();
606 CWDAndSetFileName (requestUri);
610 // Open data connection and receive data
611 case WebRequestMethods.Ftp.DownloadFile:
612 case WebRequestMethods.Ftp.ListDirectory:
613 case WebRequestMethods.Ftp.ListDirectoryDetails:
616 // Open data connection and send data
617 case WebRequestMethods.Ftp.AppendFile:
618 case WebRequestMethods.Ftp.UploadFile:
619 case WebRequestMethods.Ftp.UploadFileWithUniqueName:
622 // Get info from control connection
623 case WebRequestMethods.Ftp.GetFileSize:
624 case WebRequestMethods.Ftp.GetDateTimestamp:
625 case WebRequestMethods.Ftp.PrintWorkingDirectory:
626 case WebRequestMethods.Ftp.MakeDirectory:
627 case WebRequestMethods.Ftp.Rename:
628 case WebRequestMethods.Ftp.DeleteFile:
629 ProcessSimpleMethod ();
631 default: // What to do here?
632 throw new Exception (String.Format ("Support for command {0} not implemented yet", method));
638 private void CloseControlConnection () {
639 if (controlStream != null) {
640 SendCommand (QuitCommand);
641 controlStream.Close ();
642 controlStream = null;
646 internal void CloseDataConnection () {
647 if(origDataStream != null) {
648 origDataStream.Close ();
649 origDataStream = null;
653 private void CloseConnection () {
654 CloseControlConnection ();
655 CloseDataConnection ();
658 void ProcessSimpleMethod ()
660 State = RequestState.TransferInProgress;
664 if (method == WebRequestMethods.Ftp.PrintWorkingDirectory)
667 if (method == WebRequestMethods.Ftp.Rename)
668 method = RenameFromCommand;
670 status = SendCommand (method, file_name);
672 ftpResponse.Stream = Stream.Null;
674 string desc = status.StatusDescription;
677 case WebRequestMethods.Ftp.GetFileSize: {
678 if (status.StatusCode != FtpStatusCode.FileStatus)
679 throw CreateExceptionFromResponse (status);
683 for (i = 4, len = 0; i < desc.Length && Char.IsDigit (desc [i]); i++, len++)
687 throw new WebException ("Bad format for server response in " + method);
689 if (!Int64.TryParse (desc.Substring (4, len), out size))
690 throw new WebException ("Bad format for server response in " + method);
692 ftpResponse.contentLength = size;
695 case WebRequestMethods.Ftp.GetDateTimestamp:
696 if (status.StatusCode != FtpStatusCode.FileStatus)
697 throw CreateExceptionFromResponse (status);
698 ftpResponse.LastModified = DateTime.ParseExact (desc.Substring (4), "yyyyMMddHHmmss", null);
700 case WebRequestMethods.Ftp.MakeDirectory:
701 if (status.StatusCode != FtpStatusCode.PathnameCreated)
702 throw CreateExceptionFromResponse (status);
705 method = WebRequestMethods.Ftp.PrintWorkingDirectory;
707 if (status.StatusCode != FtpStatusCode.FileActionOK)
708 throw CreateExceptionFromResponse (status);
710 status = SendCommand (method);
712 if (status.StatusCode != FtpStatusCode.PathnameCreated)
713 throw CreateExceptionFromResponse (status);
715 case RenameFromCommand:
716 method = WebRequestMethods.Ftp.Rename;
717 if (status.StatusCode != FtpStatusCode.FileCommandPending)
718 throw CreateExceptionFromResponse (status);
719 // Pass an empty string if RenameTo wasn't specified
720 status = SendCommand (RenameToCommand, renameTo != null ? renameTo : String.Empty);
721 if (status.StatusCode != FtpStatusCode.FileActionOK)
722 throw CreateExceptionFromResponse (status);
724 case WebRequestMethods.Ftp.DeleteFile:
725 if (status.StatusCode != FtpStatusCode.FileActionOK) {
726 throw CreateExceptionFromResponse (status);
731 State = RequestState.Finished;
736 State = RequestState.OpeningData;
738 OpenDataConnection ();
740 State = RequestState.TransferInProgress;
741 requestStream = new FtpDataStream (this, dataStream, false);
742 asyncResult.Stream = requestStream;
747 State = RequestState.OpeningData;
749 OpenDataConnection ();
751 State = RequestState.TransferInProgress;
752 ftpResponse.Stream = new FtpDataStream (this, dataStream, true);
755 void CheckRequestStarted ()
757 if (State != RequestState.Before)
758 throw new InvalidOperationException ("There is a request currently in progress");
761 void OpenControlConnection ()
763 Exception exception = null;
765 foreach (IPAddress address in hostEntry.AddressList) {
766 sock = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
768 IPEndPoint remote = new IPEndPoint (address, requestUri.Port);
770 if (!ServicePoint.CallEndPointDelegate (sock, remote)) {
775 sock.Connect (remote);
776 localEndPoint = (IPEndPoint) sock.LocalEndPoint;
778 } catch (SocketException exc) {
786 // Couldn't connect to any address
788 throw new WebException ("Unable to connect to remote server", exception,
789 WebExceptionStatus.UnknownError, ftpResponse);
791 controlStream = new NetworkStream (sock);
792 controlReader = new StreamReader (controlStream, Encoding.ASCII);
794 State = RequestState.Authenticating;
797 FtpStatus status = SendCommand ("OPTS", "utf8", "on");
798 if ((int)status.StatusCode < 200 || (int)status.StatusCode > 300)
799 dataEncoding = Encoding.Default;
801 dataEncoding = Encoding.UTF8;
803 status = SendCommand (WebRequestMethods.Ftp.PrintWorkingDirectory);
804 initial_path = GetInitialPath (status);
807 static string GetInitialPath (FtpStatus status)
809 int s = (int) status.StatusCode;
810 if (s < 200 || s > 300 || status.StatusDescription.Length <= 4)
811 throw new WebException ("Error getting current directory: " + status.StatusDescription, null,
812 WebExceptionStatus.UnknownError, null);
814 string msg = status.StatusDescription.Substring (4);
815 if (msg [0] == '"') {
816 int next_quote = msg.IndexOf ('\"', 1);
817 if (next_quote == -1)
818 throw new WebException ("Error getting current directory: PWD -> " + status.StatusDescription, null,
819 WebExceptionStatus.UnknownError, null);
821 msg = msg.Substring (1, next_quote - 1);
824 if (!msg.EndsWith ("/"))
829 // Probably we could do better having here a regex
830 Socket SetupPassiveConnection (string statusDescription)
832 // Current response string
833 string response = statusDescription;
834 if (response.Length < 4)
835 throw new WebException ("Cannot open passive data connection");
837 // Look for first digit after code
839 for (i = 3; i < response.Length && !Char.IsDigit (response [i]); i++)
841 if (i >= response.Length)
842 throw new WebException ("Cannot open passive data connection");
845 string [] digits = response.Substring (i).Split (new char [] {','}, 6);
846 if (digits.Length != 6)
847 throw new WebException ("Cannot open passive data connection");
849 // Clean non-digits at the end of last element
851 for (j = digits [5].Length - 1; j >= 0 && !Char.IsDigit (digits [5][j]); j--)
854 throw new WebException ("Cannot open passive data connection");
856 digits [5] = digits [5].Substring (0, j + 1);
860 ip = IPAddress.Parse (String.Join (".", digits, 0, 4));
861 } catch (FormatException) {
862 throw new WebException ("Cannot open passive data connection");
867 if (!Int32.TryParse (digits [4], out p1) || !Int32.TryParse (digits [5], out p2))
868 throw new WebException ("Cannot open passive data connection");
870 port = (p1 << 8) + p2; // p1 * 256 + p2
871 //port = p1 * 256 + p2;
872 if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
873 throw new WebException ("Cannot open passive data connection");
875 IPEndPoint ep = new IPEndPoint (ip, port);
876 Socket sock = new Socket (ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
879 } catch (SocketException) {
881 throw new WebException ("Cannot open passive data connection");
887 Exception CreateExceptionFromResponse (FtpStatus status)
889 FtpWebResponse ftpResponse = new FtpWebResponse (this, requestUri, method, status);
891 WebException exc = new WebException ("Server returned an error: " + status.StatusDescription,
892 null, WebExceptionStatus.ProtocolError, ftpResponse);
896 // Here we could also get a server error, so be cautious
897 internal void SetTransferCompleted ()
902 State = RequestState.Finished;
903 FtpStatus status = GetResponseStatus ();
904 ftpResponse.UpdateStatus (status);
909 internal void OperationCompleted ()
915 void SetCompleteWithError (Exception exc)
917 if (asyncResult != null) {
918 asyncResult.SetCompleted (false, exc);
922 Socket InitDataConnection ()
927 status = SendCommand (PassiveCommand);
928 if (status.StatusCode != FtpStatusCode.EnteringPassive) {
929 throw CreateExceptionFromResponse (status);
932 return SetupPassiveConnection (status.StatusDescription);
935 // Open a socket to listen the server's connection
936 Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
938 sock.Bind (new IPEndPoint (localEndPoint.Address, 0));
939 sock.Listen (1); // We only expect a connection from server
941 } catch (SocketException e) {
944 throw new WebException ("Couldn't open listening socket on client", e);
947 IPEndPoint ep = (IPEndPoint) sock.LocalEndPoint;
948 string ipString = ep.Address.ToString ().Replace ('.', ',');
949 int h1 = ep.Port >> 8; // ep.Port / 256
950 int h2 = ep.Port % 256;
952 string portParam = ipString + "," + h1 + "," + h2;
953 status = SendCommand (PortCommand, portParam);
955 if (status.StatusCode != FtpStatusCode.CommandOK) {
957 throw (CreateExceptionFromResponse (status));
963 void OpenDataConnection ()
967 Socket s = InitDataConnection ();
969 // Handle content offset
971 status = SendCommand (RestCommand, offset.ToString ());
972 if (status.StatusCode != FtpStatusCode.FileCommandPending)
973 throw CreateExceptionFromResponse (status);
976 if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails &&
977 method != WebRequestMethods.Ftp.UploadFileWithUniqueName) {
978 status = SendCommand (method, file_name);
980 status = SendCommand (method);
983 if (status.StatusCode != FtpStatusCode.OpeningData && status.StatusCode != FtpStatusCode.DataAlreadyOpen)
984 throw CreateExceptionFromResponse (status);
987 origDataStream = new NetworkStream (s, true);
988 dataStream = origDataStream;
990 ChangeToSSLSocket (ref dataStream);
994 // Active connection (use Socket.Blocking to true)
995 Socket incoming = null;
997 incoming = s.Accept ();
999 catch (SocketException) {
1001 if (incoming != null)
1004 throw new ProtocolViolationException ("Server commited a protocol violation.");
1008 origDataStream = new NetworkStream (incoming, true);
1009 dataStream = origDataStream;
1011 ChangeToSSLSocket (ref dataStream);
1014 ftpResponse.UpdateStatus (status);
1017 void Authenticate ()
1019 string username = null;
1020 string password = null;
1021 string domain = null;
1023 if (credentials != null) {
1024 username = credentials.UserName;
1025 password = credentials.Password;
1026 domain = credentials.Domain;
1029 if (username == null)
1030 username = "anonymous";
1031 if (password == null)
1032 password = "@anonymous";
1033 if (!string.IsNullOrEmpty (domain))
1034 username = domain + '\\' + username;
1036 // Connect to server and get banner message
1037 FtpStatus status = GetResponseStatus ();
1038 ftpResponse.BannerMessage = status.StatusDescription;
1041 InitiateSecureConnection (ref controlStream);
1042 controlReader = new StreamReader (controlStream, Encoding.ASCII);
1043 status = SendCommand ("PBSZ", "0");
1044 int st = (int) status.StatusCode;
1045 if (st < 200 || st >= 300)
1046 throw CreateExceptionFromResponse (status);
1047 // TODO: what if "PROT P" is denied by the server? What does MS do?
1048 status = SendCommand ("PROT", "P");
1049 st = (int) status.StatusCode;
1050 if (st < 200 || st >= 300)
1051 throw CreateExceptionFromResponse (status);
1053 status = new FtpStatus (FtpStatusCode.SendUserCommand, "");
1056 if (status.StatusCode != FtpStatusCode.SendUserCommand)
1057 throw CreateExceptionFromResponse (status);
1059 status = SendCommand (UserCommand, username);
1061 switch (status.StatusCode) {
1062 case FtpStatusCode.SendPasswordCommand:
1063 status = SendCommand (PasswordCommand, password);
1064 if (status.StatusCode != FtpStatusCode.LoggedInProceed)
1065 throw CreateExceptionFromResponse (status);
1067 case FtpStatusCode.LoggedInProceed:
1070 throw CreateExceptionFromResponse (status);
1073 ftpResponse.WelcomeMessage = status.StatusDescription;
1074 ftpResponse.UpdateStatus (status);
1077 FtpStatus SendCommand (string command, params string [] parameters) {
1078 return SendCommand (true, command, parameters);
1081 FtpStatus SendCommand (bool waitResponse, string command, params string [] parameters)
1084 string commandString = command;
1085 if (parameters.Length > 0)
1086 commandString += " " + String.Join (" ", parameters);
1088 commandString += EOL;
1089 cmd = dataEncoding.GetBytes (commandString);
1091 controlStream.Write (cmd, 0, cmd.Length);
1092 } catch (IOException) {
1093 //controlStream.Close ();
1094 return new FtpStatus(FtpStatusCode.ServiceNotAvailable, "Write failed");
1100 FtpStatus result = GetResponseStatus ();
1101 if (ftpResponse != null)
1102 ftpResponse.UpdateStatus (result);
1106 internal static FtpStatus ServiceNotAvailable ()
1108 return new FtpStatus (FtpStatusCode.ServiceNotAvailable, Locale.GetText ("Invalid response from server"));
1111 internal FtpStatus GetResponseStatus ()
1114 string response = null;
1117 response = controlReader.ReadLine ();
1118 } catch (IOException) {
1121 if (response == null || response.Length < 3)
1122 return ServiceNotAvailable ();
1125 if (!Int32.TryParse (response.Substring (0, 3), out code))
1126 return ServiceNotAvailable ();
1128 if (response.Length > 3 && response [3] == '-'){
1130 string find = code.ToString() + ' ';
1134 line = controlReader.ReadLine();
1135 } catch (IOException) {
1138 return ServiceNotAvailable ();
1140 response += Environment.NewLine + line;
1142 if (line.StartsWith(find, StringComparison.Ordinal))
1146 return new FtpStatus ((FtpStatusCode) code, response);
1150 private void InitiateSecureConnection (ref Stream stream) {
1151 FtpStatus status = SendCommand (AuthCommand, "TLS");
1152 if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession)
1153 throw CreateExceptionFromResponse (status);
1155 ChangeToSSLSocket (ref stream);
1159 RemoteCertificateValidationCallback callback = delegate (object sender,
1160 X509Certificate certificate,
1162 SslPolicyErrors sslPolicyErrors) {
1163 // honor any exciting callback defined on ServicePointManager
1164 if (ServicePointManager.ServerCertificateValidationCallback != null)
1165 return ServicePointManager.ServerCertificateValidationCallback (sender, certificate, chain, sslPolicyErrors);
1166 // otherwise provide our own
1167 if (sslPolicyErrors != SslPolicyErrors.None)
1168 throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors);
1173 internal bool ChangeToSSLSocket (ref Stream stream) {
1175 SslStream sslStream = new SslStream (stream, true, callback, null);
1176 //sslStream.AuthenticateAsClient (Host, this.ClientCertificates, SslProtocols.Default, false);
1177 //TODO: client certificates
1178 sslStream.AuthenticateAsClient (requestUri.Host, null, SslProtocols.Default, false);
1182 throw new NotImplementedException ();
1186 bool InFinalState () {
1187 return (State == RequestState.Aborted || State == RequestState.Error || State == RequestState.Finished);
1190 bool InProgress () {
1191 return (State != RequestState.Before && !InFinalState ());
1194 internal void CheckIfAborted () {
1195 if (State == RequestState.Aborted)
1196 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
1199 void CheckFinalState () {
1200 if (InFinalState ())
1201 throw new InvalidOperationException ("Cannot change final state");