2 // System.Net.FtpWebRequest.cs
5 // Carlos Alberto Cortez (calberto.cortez@gmail.com)
7 // (c) Copyright 2006 Novell, Inc. (http://www.novell.com)
11 #if MONO_SECURITY_ALIAS
12 extern alias MonoSecurity;
13 using MSI = MonoSecurity::Mono.Security.Interface;
15 using MSI = Mono.Security.Interface;
20 using System.Globalization;
22 using System.Net.Sockets;
24 using System.Threading;
25 using System.Net.Cache;
26 using System.Security.Cryptography.X509Certificates;
28 using System.Net.Security;
29 using System.Security.Authentication;
30 using Mono.Net.Security;
34 public sealed class FtpWebRequest : WebRequest
37 string file_name; // By now, used for upload
38 ServicePoint servicePoint;
39 Stream origDataStream;
42 StreamReader controlReader;
43 NetworkCredential credentials;
44 IPHostEntry hostEntry;
45 IPEndPoint localEndPoint;
46 IPEndPoint remoteEndPoint;
49 int rwTimeout = 300000;
52 bool enableSsl = false;
53 bool usePassive = true;
54 bool keepAlive = false;
55 string method = WebRequestMethods.Ftp.DownloadFile;
57 object locker = new object ();
59 RequestState requestState = RequestState.Before;
60 FtpAsyncResult asyncResult;
61 FtpWebResponse ftpResponse;
65 const string ChangeDir = "CWD";
66 const string UserCommand = "USER";
67 const string PasswordCommand = "PASS";
68 const string TypeCommand = "TYPE";
69 const string PassiveCommand = "PASV";
70 const string ExtendedPassiveCommand = "EPSV";
71 const string PortCommand = "PORT";
72 const string ExtendedPortCommand = "EPRT";
73 const string AbortCommand = "ABOR";
74 const string AuthCommand = "AUTH";
75 const string RestCommand = "REST";
76 const string RenameFromCommand = "RNFR";
77 const string RenameToCommand = "RNTO";
78 const string QuitCommand = "QUIT";
79 const string EOL = "\r\n"; // Special end of line
95 static readonly string [] supportedCommands = new string [] {
96 WebRequestMethods.Ftp.AppendFile, // APPE
97 WebRequestMethods.Ftp.DeleteFile, // DELE
98 WebRequestMethods.Ftp.ListDirectoryDetails, // LIST
99 WebRequestMethods.Ftp.GetDateTimestamp, // MDTM
100 WebRequestMethods.Ftp.MakeDirectory, // MKD
101 WebRequestMethods.Ftp.ListDirectory, // NLST
102 WebRequestMethods.Ftp.PrintWorkingDirectory, // PWD
103 WebRequestMethods.Ftp.Rename, // RENAME
104 WebRequestMethods.Ftp.DownloadFile, // RETR
105 WebRequestMethods.Ftp.RemoveDirectory, // RMD
106 WebRequestMethods.Ftp.GetFileSize, // SIZE
107 WebRequestMethods.Ftp.UploadFile, // STOR
108 WebRequestMethods.Ftp.UploadFileWithUniqueName // STUR
111 Encoding dataEncoding = Encoding.UTF8;
113 internal FtpWebRequest (Uri uri)
115 this.requestUri = uri;
116 this.proxy = GlobalProxySelection.Select;
119 static Exception GetMustImplement ()
121 return new NotImplementedException ();
125 public X509CertificateCollection ClientCertificates
128 throw GetMustImplement ();
131 throw GetMustImplement ();
136 public override string ConnectionGroupName
139 throw GetMustImplement ();
142 throw GetMustImplement ();
146 public override string ContentType {
148 throw new NotSupportedException ();
151 throw new NotSupportedException ();
155 public override long ContentLength {
164 public long ContentOffset {
169 CheckRequestStarted ();
171 throw new ArgumentOutOfRangeException ();
177 public override ICredentials Credentials {
182 CheckRequestStarted ();
184 throw new ArgumentNullException ();
185 if (!(value is NetworkCredential))
186 throw new ArgumentException ();
188 credentials = value as NetworkCredential;
194 public static new RequestCachePolicy DefaultCachePolicy
197 throw GetMustImplement ();
200 throw GetMustImplement ();
205 public bool EnableSsl {
210 CheckRequestStarted ();
216 public override WebHeaderCollection Headers
219 throw GetMustImplement ();
222 throw GetMustImplement ();
226 [MonoTODO ("We don't support KeepAlive = true")]
227 public bool KeepAlive {
232 CheckRequestStarted ();
237 public override string Method {
242 CheckRequestStarted ();
244 throw new ArgumentNullException ("Method string cannot be null");
246 if (value.Length == 0 || Array.BinarySearch (supportedCommands, value) < 0)
247 throw new ArgumentException ("Method not supported", "value");
253 public override bool PreAuthenticate {
255 throw new NotSupportedException ();
258 throw new NotSupportedException ();
262 public override IWebProxy Proxy {
267 CheckRequestStarted ();
272 public int ReadWriteTimeout {
277 CheckRequestStarted ();
280 throw new ArgumentOutOfRangeException ();
286 public string RenameTo {
291 CheckRequestStarted ();
292 if (value == null || value.Length == 0)
293 throw new ArgumentException ("RenameTo value can't be null or empty", "RenameTo");
299 public override Uri RequestUri {
305 public ServicePoint ServicePoint {
307 return GetServicePoint ();
311 public bool UsePassive {
316 CheckRequestStarted ();
322 public override bool UseDefaultCredentials
325 throw GetMustImplement ();
328 throw GetMustImplement ();
332 public bool UseBinary {
336 CheckRequestStarted ();
341 public override int Timeout {
346 CheckRequestStarted ();
349 throw new ArgumentOutOfRangeException ();
357 return binary ? "I" : "A";
372 requestState = value;
377 public override void Abort () {
379 if (State == RequestState.TransferInProgress) {
380 /*FtpStatus status = */
381 SendCommand (false, AbortCommand);
384 if (!InFinalState ()) {
385 State = RequestState.Aborted;
386 ftpResponse = new FtpWebResponse (this, requestUri, method, FtpStatusCode.FileActionAborted, "Aborted by request");
391 public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state) {
392 if (asyncResult != null && !asyncResult.IsCompleted) {
393 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
398 asyncResult = new FtpAsyncResult (callback, state);
402 asyncResult.SetCompleted (true, ftpResponse);
404 if (State == RequestState.Before)
405 State = RequestState.Scheduled;
407 Thread thread = new Thread (ProcessRequest);
408 thread.IsBackground = true;
416 public override WebResponse EndGetResponse (IAsyncResult asyncResult) {
417 if (asyncResult == null)
418 throw new ArgumentNullException ("AsyncResult cannot be null!");
420 if (!(asyncResult is FtpAsyncResult) || asyncResult != this.asyncResult)
421 throw new ArgumentException ("AsyncResult is from another request!");
423 FtpAsyncResult asyncFtpResult = (FtpAsyncResult) asyncResult;
424 if (!asyncFtpResult.WaitUntilComplete (timeout, false)) {
426 throw new WebException ("Transfer timed out.", WebExceptionStatus.Timeout);
433 if (asyncFtpResult.GotException)
434 throw asyncFtpResult.Exception;
436 return asyncFtpResult.Response;
439 public override WebResponse GetResponse () {
440 IAsyncResult asyncResult = BeginGetResponse (null, null);
441 return EndGetResponse (asyncResult);
444 public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state) {
445 if (method != WebRequestMethods.Ftp.UploadFile && method != WebRequestMethods.Ftp.UploadFileWithUniqueName &&
446 method != WebRequestMethods.Ftp.AppendFile)
447 throw new ProtocolViolationException ();
452 if (State != RequestState.Before)
453 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
455 State = RequestState.Scheduled;
458 asyncResult = new FtpAsyncResult (callback, state);
459 Thread thread = new Thread (ProcessRequest);
460 thread.IsBackground = true;
466 public override Stream EndGetRequestStream (IAsyncResult asyncResult) {
467 if (asyncResult == null)
468 throw new ArgumentNullException ("asyncResult");
470 if (!(asyncResult is FtpAsyncResult))
471 throw new ArgumentException ("asyncResult");
473 if (State == RequestState.Aborted) {
474 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
477 if (asyncResult != this.asyncResult)
478 throw new ArgumentException ("AsyncResult is from another request!");
480 FtpAsyncResult res = (FtpAsyncResult) asyncResult;
482 if (!res.WaitUntilComplete (timeout, false)) {
484 throw new WebException ("Request timed out");
487 if (res.GotException)
493 public override Stream GetRequestStream () {
494 IAsyncResult asyncResult = BeginGetRequestStream (null, null);
495 return EndGetRequestStream (asyncResult);
498 ServicePoint GetServicePoint ()
500 if (servicePoint == null)
501 servicePoint = ServicePointManager.FindServicePoint (requestUri, proxy);
506 // Probably move some code of command connection here
510 hostEntry = GetServicePoint ().HostEntry;
512 if (hostEntry == null) {
513 ftpResponse.UpdateStatus (new FtpStatus(FtpStatusCode.ActionAbortedLocalProcessingError, "Cannot resolve server name"));
514 throw new WebException ("The remote server name could not be resolved: " + requestUri,
515 null, WebExceptionStatus.NameResolutionFailure, ftpResponse);
519 void ProcessRequest () {
521 if (State == RequestState.Scheduled) {
522 ftpResponse = new FtpWebResponse (this, requestUri, method, keepAlive);
526 //State = RequestState.Finished;
527 //finalResponse = ftpResponse;
528 asyncResult.SetCompleted (false, ftpResponse);
530 catch (Exception e) {
531 if (!GetServicePoint ().UsesProxy)
532 State = RequestState.Error;
533 SetCompleteWithError (e);
538 FtpStatus status = GetResponseStatus ();
540 ftpResponse.UpdateStatus (status);
542 if (ftpResponse.IsFinal ()) {
543 State = RequestState.Finished;
547 asyncResult.SetCompleted (false, ftpResponse);
554 FtpStatus status = SendCommand (TypeCommand, DataType);
555 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
556 throw CreateExceptionFromResponse (status);
560 string GetRemoteFolderPath (Uri uri)
563 string local_path = Uri.UnescapeDataString (uri.LocalPath);
564 if (initial_path == null || initial_path == "/") {
567 if (local_path [0] == '/')
568 local_path = local_path.Substring (1);
570 UriBuilder initialBuilder = new UriBuilder () {
575 Uri initial = initialBuilder.Uri;
576 result = new Uri (initial, local_path).LocalPath;
579 int last = result.LastIndexOf ('/');
583 return result.Substring (0, last + 1);
586 void CWDAndSetFileName (Uri uri)
588 string remote_folder = GetRemoteFolderPath (uri);
590 if (remote_folder != null) {
591 status = SendCommand (ChangeDir, remote_folder);
592 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
593 throw CreateExceptionFromResponse (status);
595 int last = uri.LocalPath.LastIndexOf ('/');
597 file_name = Uri.UnescapeDataString (uri.LocalPath.Substring (last + 1));
602 void ProcessMethod ()
604 ServicePoint sp = GetServicePoint ();
606 if (method != WebRequestMethods.Ftp.DownloadFile)
607 throw new NotSupportedException ("FTP+proxy only supports RETR");
609 HttpWebRequest req = (HttpWebRequest) WebRequest.Create (proxy.GetProxy (requestUri));
610 req.Address = requestUri;
611 requestState = RequestState.Finished;
612 WebResponse response = req.GetResponse ();
613 ftpResponse.Stream = new FtpDataStream (this, response.GetResponseStream (), true);
614 ftpResponse.StatusCode = FtpStatusCode.CommandOK;
617 State = RequestState.Connecting;
621 OpenControlConnection ();
622 CWDAndSetFileName (requestUri);
626 // Open data connection and receive data
627 case WebRequestMethods.Ftp.DownloadFile:
628 case WebRequestMethods.Ftp.ListDirectory:
629 case WebRequestMethods.Ftp.ListDirectoryDetails:
632 // Open data connection and send data
633 case WebRequestMethods.Ftp.AppendFile:
634 case WebRequestMethods.Ftp.UploadFile:
635 case WebRequestMethods.Ftp.UploadFileWithUniqueName:
638 // Get info from control connection
639 case WebRequestMethods.Ftp.GetFileSize:
640 case WebRequestMethods.Ftp.GetDateTimestamp:
641 case WebRequestMethods.Ftp.PrintWorkingDirectory:
642 case WebRequestMethods.Ftp.MakeDirectory:
643 case WebRequestMethods.Ftp.Rename:
644 case WebRequestMethods.Ftp.DeleteFile:
645 ProcessSimpleMethod ();
647 default: // What to do here?
648 throw new Exception (String.Format ("Support for command {0} not implemented yet", method));
654 private void CloseControlConnection () {
655 if (controlStream != null) {
656 SendCommand (QuitCommand);
657 controlStream.Close ();
658 controlStream = null;
662 internal void CloseDataConnection () {
663 if(origDataStream != null) {
664 origDataStream.Close ();
665 origDataStream = null;
669 private void CloseConnection () {
670 CloseControlConnection ();
671 CloseDataConnection ();
674 void ProcessSimpleMethod ()
676 State = RequestState.TransferInProgress;
680 if (method == WebRequestMethods.Ftp.PrintWorkingDirectory)
683 if (method == WebRequestMethods.Ftp.Rename)
684 method = RenameFromCommand;
686 status = SendCommand (method, file_name);
688 ftpResponse.Stream = Stream.Null;
690 string desc = status.StatusDescription;
693 case WebRequestMethods.Ftp.GetFileSize: {
694 if (status.StatusCode != FtpStatusCode.FileStatus)
695 throw CreateExceptionFromResponse (status);
699 for (i = 4, len = 0; i < desc.Length && Char.IsDigit (desc [i]); i++, len++)
703 throw new WebException ("Bad format for server response in " + method);
705 if (!Int64.TryParse (desc.Substring (4, len), out size))
706 throw new WebException ("Bad format for server response in " + method);
708 ftpResponse.contentLength = size;
711 case WebRequestMethods.Ftp.GetDateTimestamp:
712 if (status.StatusCode != FtpStatusCode.FileStatus)
713 throw CreateExceptionFromResponse (status);
714 ftpResponse.LastModified = DateTime.ParseExact (desc.Substring (4), "yyyyMMddHHmmss", null);
716 case WebRequestMethods.Ftp.MakeDirectory:
717 if (status.StatusCode != FtpStatusCode.PathnameCreated)
718 throw CreateExceptionFromResponse (status);
721 method = WebRequestMethods.Ftp.PrintWorkingDirectory;
723 if (status.StatusCode != FtpStatusCode.FileActionOK)
724 throw CreateExceptionFromResponse (status);
726 status = SendCommand (method);
728 if (status.StatusCode != FtpStatusCode.PathnameCreated)
729 throw CreateExceptionFromResponse (status);
731 case RenameFromCommand:
732 method = WebRequestMethods.Ftp.Rename;
733 if (status.StatusCode != FtpStatusCode.FileCommandPending)
734 throw CreateExceptionFromResponse (status);
735 // Pass an empty string if RenameTo wasn't specified
736 status = SendCommand (RenameToCommand, renameTo != null ? renameTo : String.Empty);
737 if (status.StatusCode != FtpStatusCode.FileActionOK)
738 throw CreateExceptionFromResponse (status);
740 case WebRequestMethods.Ftp.DeleteFile:
741 if (status.StatusCode != FtpStatusCode.FileActionOK) {
742 throw CreateExceptionFromResponse (status);
747 State = RequestState.Finished;
752 State = RequestState.OpeningData;
754 OpenDataConnection ();
756 State = RequestState.TransferInProgress;
757 requestStream = new FtpDataStream (this, dataStream, false);
758 asyncResult.Stream = requestStream;
763 State = RequestState.OpeningData;
765 OpenDataConnection ();
767 State = RequestState.TransferInProgress;
768 ftpResponse.Stream = new FtpDataStream (this, dataStream, true);
771 void CheckRequestStarted ()
773 if (State != RequestState.Before)
774 throw new InvalidOperationException ("There is a request currently in progress");
777 void OpenControlConnection ()
779 Exception exception = null;
781 foreach (IPAddress address in hostEntry.AddressList) {
782 sock = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
784 remoteEndPoint = new IPEndPoint (address, requestUri.Port);
786 if (!ServicePoint.CallEndPointDelegate (sock, remoteEndPoint)) {
791 sock.Connect (remoteEndPoint);
792 localEndPoint = (IPEndPoint) sock.LocalEndPoint;
794 } catch (SocketException exc) {
802 // Couldn't connect to any address
804 throw new WebException ("Unable to connect to remote server", exception,
805 WebExceptionStatus.UnknownError, ftpResponse);
807 controlStream = new NetworkStream (sock);
808 controlReader = new StreamReader (controlStream, Encoding.ASCII);
810 State = RequestState.Authenticating;
813 FtpStatus status = SendCommand ("OPTS", "utf8", "on");
814 if ((int)status.StatusCode < 200 || (int)status.StatusCode > 300)
815 dataEncoding = Encoding.Default;
817 dataEncoding = Encoding.UTF8;
819 status = SendCommand (WebRequestMethods.Ftp.PrintWorkingDirectory);
820 initial_path = GetInitialPath (status);
823 static string GetInitialPath (FtpStatus status)
825 int s = (int) status.StatusCode;
826 if (s < 200 || s > 300 || status.StatusDescription.Length <= 4)
827 throw new WebException ("Error getting current directory: " + status.StatusDescription, null,
828 WebExceptionStatus.UnknownError, null);
830 string msg = status.StatusDescription.Substring (4);
831 if (msg [0] == '"') {
832 int next_quote = msg.IndexOf ('\"', 1);
833 if (next_quote == -1)
834 throw new WebException ("Error getting current directory: PWD -> " + status.StatusDescription, null,
835 WebExceptionStatus.UnknownError, null);
837 msg = msg.Substring (1, next_quote - 1);
840 if (!msg.EndsWith ("/"))
845 // Probably we could do better having here a regex
846 Socket SetupPassiveConnection (string statusDescription, bool ipv6)
848 // Current response string
849 string response = statusDescription;
850 if (response.Length < 4)
851 throw new WebException ("Cannot open passive data connection");
853 int port = ipv6 ? GetPortV6 (response) : GetPortV4 (response);
855 if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
856 throw new WebException ("Cannot open passive data connection");
858 IPEndPoint ep = new IPEndPoint (remoteEndPoint.Address, port);
859 Socket sock = new Socket (ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
862 } catch (SocketException) {
864 throw new WebException ("Cannot open passive data connection");
870 // GetPortV4, GetPortV6, FormatAddress and FormatAddressV6 are copied from referencesource
871 // TODO: replace FtpWebRequest completely.
872 private int GetPortV4(string responseString)
874 string [] parsedList = responseString.Split(new char [] {' ', '(', ',', ')'});
876 // We need at least the status code and the port
877 if (parsedList.Length <= 7) {
878 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
881 int index = parsedList.Length-1;
882 // skip the last non-number token (e.g. terminating '.')
884 // the MS code expects \r\n here in parsedList[index],
885 // but we're stripping the EOL off earlier so the array contains
886 // an empty string here which would make Char.IsNumber throw
887 // TODO: this can be removed once we switch FtpWebRequest to referencesource
888 if (parsedList[index] == "" || !Char.IsNumber(parsedList[index], 0))
890 if (!Char.IsNumber(parsedList[index], 0))
894 int port = Convert.ToByte(parsedList[index--], NumberFormatInfo.InvariantInfo);
896 (Convert.ToByte(parsedList[index--], NumberFormatInfo.InvariantInfo) << 8);
901 private int GetPortV6(string responseString)
903 int pos1 = responseString.LastIndexOf("(");
904 int pos2 = responseString.LastIndexOf(")");
905 if (pos1 == -1 || pos2 <= pos1)
906 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
908 // addressInfo will contain a string of format "|||<tcp-port>|"
909 string addressInfo = responseString.Substring(pos1+1, pos2-pos1-1);
911 // Although RFC2428 recommends using "|" as the delimiter,
912 // It allows ASCII characters in range 33-126 inclusive.
913 // We should consider allowing the full range.
915 string [] parsedList = addressInfo.Split(new char [] {'|'});
916 if (parsedList.Length < 4)
917 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
919 return Convert.ToInt32(parsedList[3], NumberFormatInfo.InvariantInfo);
922 private String FormatAddress(IPAddress address, int Port )
924 byte [] localAddressInBytes = address.GetAddressBytes();
926 // produces a string in FTP IPAddress/Port encoding (a1, a2, a3, a4, p1, p2), for sending as a parameter
927 // to the port command.
928 StringBuilder sb = new StringBuilder(32);
929 foreach (byte element in localAddressInBytes) {
933 sb.Append(Port / 256 );
935 sb.Append(Port % 256 );
936 return sb.ToString();
939 private string FormatAddressV6(IPAddress address, int port) {
940 StringBuilder sb = new StringBuilder(43); // based on max size of IPv6 address + port + seperators
941 String addressString = address.ToString();
943 sb.Append(addressString);
945 sb.Append(port.ToString(NumberFormatInfo.InvariantInfo));
947 return sb.ToString();
951 Exception CreateExceptionFromResponse (FtpStatus status)
953 FtpWebResponse ftpResponse = new FtpWebResponse (this, requestUri, method, status);
955 WebException exc = new WebException ("Server returned an error: " + status.StatusDescription,
956 null, WebExceptionStatus.ProtocolError, ftpResponse);
960 // Here we could also get a server error, so be cautious
961 internal void SetTransferCompleted ()
966 State = RequestState.Finished;
967 FtpStatus status = GetResponseStatus ();
968 ftpResponse.UpdateStatus (status);
973 internal void OperationCompleted ()
979 void SetCompleteWithError (Exception exc)
981 if (asyncResult != null) {
982 asyncResult.SetCompleted (false, exc);
986 Socket InitDataConnection ()
989 bool ipv6 = remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6;
992 status = SendCommand (ipv6 ? ExtendedPassiveCommand : PassiveCommand);
993 if (status.StatusCode != (ipv6 ? (FtpStatusCode)229 : FtpStatusCode.EnteringPassive)) { // FtpStatusCode doesn't contain code 229 for EPSV so we need to cast...
994 throw CreateExceptionFromResponse (status);
997 return SetupPassiveConnection (status.StatusDescription, ipv6);
1000 // Open a socket to listen the server's connection
1001 Socket sock = new Socket (remoteEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
1003 sock.Bind (new IPEndPoint (localEndPoint.Address, 0));
1004 sock.Listen (1); // We only expect a connection from server
1006 } catch (SocketException e) {
1009 throw new WebException ("Couldn't open listening socket on client", e);
1012 IPEndPoint ep = (IPEndPoint) sock.LocalEndPoint;
1014 var portParam = ipv6 ? FormatAddressV6 (ep.Address, ep.Port) : FormatAddress (ep.Address, ep.Port);
1016 status = SendCommand (ipv6 ? ExtendedPortCommand : PortCommand, portParam);
1018 if (status.StatusCode != FtpStatusCode.CommandOK) {
1020 throw (CreateExceptionFromResponse (status));
1026 void OpenDataConnection ()
1030 Socket s = InitDataConnection ();
1032 // Handle content offset
1034 status = SendCommand (RestCommand, offset.ToString ());
1035 if (status.StatusCode != FtpStatusCode.FileCommandPending)
1036 throw CreateExceptionFromResponse (status);
1039 if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails &&
1040 method != WebRequestMethods.Ftp.UploadFileWithUniqueName) {
1041 status = SendCommand (method, file_name);
1043 status = SendCommand (method);
1046 if (status.StatusCode != FtpStatusCode.OpeningData && status.StatusCode != FtpStatusCode.DataAlreadyOpen)
1047 throw CreateExceptionFromResponse (status);
1050 origDataStream = new NetworkStream (s, true);
1051 dataStream = origDataStream;
1053 ChangeToSSLSocket (ref dataStream);
1057 // Active connection (use Socket.Blocking to true)
1058 Socket incoming = null;
1060 incoming = s.Accept ();
1062 catch (SocketException) {
1064 if (incoming != null)
1067 throw new ProtocolViolationException ("Server commited a protocol violation.");
1071 origDataStream = new NetworkStream (incoming, true);
1072 dataStream = origDataStream;
1074 ChangeToSSLSocket (ref dataStream);
1077 ftpResponse.UpdateStatus (status);
1080 void Authenticate ()
1082 string username = null;
1083 string password = null;
1084 string domain = null;
1086 if (credentials != null) {
1087 username = credentials.UserName;
1088 password = credentials.Password;
1089 domain = credentials.Domain;
1092 if (username == null)
1093 username = "anonymous";
1094 if (password == null)
1095 password = "@anonymous";
1096 if (!string.IsNullOrEmpty (domain))
1097 username = domain + '\\' + username;
1099 // Connect to server and get banner message
1100 FtpStatus status = GetResponseStatus ();
1101 ftpResponse.BannerMessage = status.StatusDescription;
1104 InitiateSecureConnection (ref controlStream);
1105 controlReader = new StreamReader (controlStream, Encoding.ASCII);
1106 status = SendCommand ("PBSZ", "0");
1107 int st = (int) status.StatusCode;
1108 if (st < 200 || st >= 300)
1109 throw CreateExceptionFromResponse (status);
1110 // TODO: what if "PROT P" is denied by the server? What does MS do?
1111 status = SendCommand ("PROT", "P");
1112 st = (int) status.StatusCode;
1113 if (st < 200 || st >= 300)
1114 throw CreateExceptionFromResponse (status);
1116 status = new FtpStatus (FtpStatusCode.SendUserCommand, "");
1119 if (status.StatusCode != FtpStatusCode.SendUserCommand)
1120 throw CreateExceptionFromResponse (status);
1122 status = SendCommand (UserCommand, username);
1124 switch (status.StatusCode) {
1125 case FtpStatusCode.SendPasswordCommand:
1126 status = SendCommand (PasswordCommand, password);
1127 if (status.StatusCode != FtpStatusCode.LoggedInProceed)
1128 throw CreateExceptionFromResponse (status);
1130 case FtpStatusCode.LoggedInProceed:
1133 throw CreateExceptionFromResponse (status);
1136 ftpResponse.WelcomeMessage = status.StatusDescription;
1137 ftpResponse.UpdateStatus (status);
1140 FtpStatus SendCommand (string command, params string [] parameters) {
1141 return SendCommand (true, command, parameters);
1144 FtpStatus SendCommand (bool waitResponse, string command, params string [] parameters)
1147 string commandString = command;
1148 if (parameters.Length > 0)
1149 commandString += " " + String.Join (" ", parameters);
1151 commandString += EOL;
1152 cmd = dataEncoding.GetBytes (commandString);
1154 controlStream.Write (cmd, 0, cmd.Length);
1155 } catch (IOException) {
1156 //controlStream.Close ();
1157 return new FtpStatus(FtpStatusCode.ServiceNotAvailable, "Write failed");
1163 FtpStatus result = GetResponseStatus ();
1164 if (ftpResponse != null)
1165 ftpResponse.UpdateStatus (result);
1169 internal static FtpStatus ServiceNotAvailable ()
1171 return new FtpStatus (FtpStatusCode.ServiceNotAvailable, Locale.GetText ("Invalid response from server"));
1174 internal FtpStatus GetResponseStatus ()
1177 string response = null;
1180 response = controlReader.ReadLine ();
1181 } catch (IOException) {
1184 if (response == null || response.Length < 3)
1185 return ServiceNotAvailable ();
1188 if (!Int32.TryParse (response.Substring (0, 3), out code))
1189 return ServiceNotAvailable ();
1191 if (response.Length > 3 && response [3] == '-'){
1193 string find = code.ToString() + ' ';
1197 line = controlReader.ReadLine();
1198 } catch (IOException) {
1201 return ServiceNotAvailable ();
1203 response += Environment.NewLine + line;
1205 if (line.StartsWith(find, StringComparison.Ordinal))
1209 return new FtpStatus ((FtpStatusCode) code, response);
1213 private void InitiateSecureConnection (ref Stream stream) {
1214 FtpStatus status = SendCommand (AuthCommand, "TLS");
1215 if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession)
1216 throw CreateExceptionFromResponse (status);
1218 ChangeToSSLSocket (ref stream);
1221 internal bool ChangeToSSLSocket (ref Stream stream) {
1223 var provider = MonoTlsProviderFactory.GetProviderInternal ();
1224 var settings = MSI.MonoTlsSettings.CopyDefaultSettings ();
1225 settings.UseServicePointManagerCallback = true;
1226 var sslStream = provider.CreateSslStream (stream, true, settings);
1227 sslStream.AuthenticateAsClient (requestUri.Host, null, SslProtocols.Default, false);
1228 stream = sslStream.AuthenticatedStream;
1231 throw new NotImplementedException ();
1235 bool InFinalState () {
1236 return (State == RequestState.Aborted || State == RequestState.Error || State == RequestState.Finished);
1239 bool InProgress () {
1240 return (State != RequestState.Before && !InFinalState ());
1243 internal void CheckIfAborted () {
1244 if (State == RequestState.Aborted)
1245 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
1248 void CheckFinalState () {
1249 if (InFinalState ())
1250 throw new InvalidOperationException ("Cannot change final state");