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 #pragma warning disable 618
117 this.proxy = GlobalProxySelection.Select;
118 #pragma warning restore 618
121 static Exception GetMustImplement ()
123 return new NotImplementedException ();
127 public X509CertificateCollection ClientCertificates
130 throw GetMustImplement ();
133 throw GetMustImplement ();
138 public override string ConnectionGroupName
141 throw GetMustImplement ();
144 throw GetMustImplement ();
148 public override string ContentType {
150 throw new NotSupportedException ();
153 throw new NotSupportedException ();
157 public override long ContentLength {
166 public long ContentOffset {
171 CheckRequestStarted ();
173 throw new ArgumentOutOfRangeException ();
179 public override ICredentials Credentials {
184 CheckRequestStarted ();
186 throw new ArgumentNullException ();
187 if (!(value is NetworkCredential))
188 throw new ArgumentException ();
190 credentials = value as NetworkCredential;
196 public static new RequestCachePolicy DefaultCachePolicy
199 throw GetMustImplement ();
202 throw GetMustImplement ();
207 public bool EnableSsl {
212 CheckRequestStarted ();
218 public override WebHeaderCollection Headers
221 throw GetMustImplement ();
224 throw GetMustImplement ();
228 [MonoTODO ("We don't support KeepAlive = true")]
229 public bool KeepAlive {
234 CheckRequestStarted ();
239 public override string Method {
244 CheckRequestStarted ();
246 throw new ArgumentNullException ("Method string cannot be null");
248 if (value.Length == 0 || Array.BinarySearch (supportedCommands, value) < 0)
249 throw new ArgumentException ("Method not supported", "value");
255 public override bool PreAuthenticate {
257 throw new NotSupportedException ();
260 throw new NotSupportedException ();
264 public override IWebProxy Proxy {
269 CheckRequestStarted ();
274 public int ReadWriteTimeout {
279 CheckRequestStarted ();
282 throw new ArgumentOutOfRangeException ();
288 public string RenameTo {
293 CheckRequestStarted ();
294 if (value == null || value.Length == 0)
295 throw new ArgumentException ("RenameTo value can't be null or empty", "RenameTo");
301 public override Uri RequestUri {
307 public ServicePoint ServicePoint {
309 return GetServicePoint ();
313 public bool UsePassive {
318 CheckRequestStarted ();
324 public override bool UseDefaultCredentials
327 throw GetMustImplement ();
330 throw GetMustImplement ();
334 public bool UseBinary {
338 CheckRequestStarted ();
343 public override int Timeout {
348 CheckRequestStarted ();
351 throw new ArgumentOutOfRangeException ();
359 return binary ? "I" : "A";
374 requestState = value;
379 public override void Abort () {
381 if (State == RequestState.TransferInProgress) {
382 /*FtpStatus status = */
383 SendCommand (false, AbortCommand);
386 if (!InFinalState ()) {
387 State = RequestState.Aborted;
388 ftpResponse = new FtpWebResponse (this, requestUri, method, FtpStatusCode.FileActionAborted, "Aborted by request");
393 public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state) {
394 if (asyncResult != null && !asyncResult.IsCompleted) {
395 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
400 asyncResult = new FtpAsyncResult (callback, state);
404 asyncResult.SetCompleted (true, ftpResponse);
406 if (State == RequestState.Before)
407 State = RequestState.Scheduled;
409 Thread thread = new Thread (ProcessRequest);
410 thread.IsBackground = true;
418 public override WebResponse EndGetResponse (IAsyncResult asyncResult) {
419 if (asyncResult == null)
420 throw new ArgumentNullException ("AsyncResult cannot be null!");
422 if (!(asyncResult is FtpAsyncResult) || asyncResult != this.asyncResult)
423 throw new ArgumentException ("AsyncResult is from another request!");
425 FtpAsyncResult asyncFtpResult = (FtpAsyncResult) asyncResult;
426 if (!asyncFtpResult.WaitUntilComplete (timeout, false)) {
428 throw new WebException ("Transfer timed out.", WebExceptionStatus.Timeout);
435 if (asyncFtpResult.GotException)
436 throw asyncFtpResult.Exception;
438 return asyncFtpResult.Response;
441 public override WebResponse GetResponse () {
442 IAsyncResult asyncResult = BeginGetResponse (null, null);
443 return EndGetResponse (asyncResult);
446 public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state) {
447 if (method != WebRequestMethods.Ftp.UploadFile && method != WebRequestMethods.Ftp.UploadFileWithUniqueName &&
448 method != WebRequestMethods.Ftp.AppendFile)
449 throw new ProtocolViolationException ();
454 if (State != RequestState.Before)
455 throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
457 State = RequestState.Scheduled;
460 asyncResult = new FtpAsyncResult (callback, state);
461 Thread thread = new Thread (ProcessRequest);
462 thread.IsBackground = true;
468 public override Stream EndGetRequestStream (IAsyncResult asyncResult) {
469 if (asyncResult == null)
470 throw new ArgumentNullException ("asyncResult");
472 if (!(asyncResult is FtpAsyncResult))
473 throw new ArgumentException ("asyncResult");
475 if (State == RequestState.Aborted) {
476 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
479 if (asyncResult != this.asyncResult)
480 throw new ArgumentException ("AsyncResult is from another request!");
482 FtpAsyncResult res = (FtpAsyncResult) asyncResult;
484 if (!res.WaitUntilComplete (timeout, false)) {
486 throw new WebException ("Request timed out");
489 if (res.GotException)
495 public override Stream GetRequestStream () {
496 IAsyncResult asyncResult = BeginGetRequestStream (null, null);
497 return EndGetRequestStream (asyncResult);
500 ServicePoint GetServicePoint ()
502 if (servicePoint == null)
503 servicePoint = ServicePointManager.FindServicePoint (requestUri, proxy);
508 // Probably move some code of command connection here
512 hostEntry = GetServicePoint ().HostEntry;
514 if (hostEntry == null) {
515 ftpResponse.UpdateStatus (new FtpStatus(FtpStatusCode.ActionAbortedLocalProcessingError, "Cannot resolve server name"));
516 throw new WebException ("The remote server name could not be resolved: " + requestUri,
517 null, WebExceptionStatus.NameResolutionFailure, ftpResponse);
521 void ProcessRequest () {
523 if (State == RequestState.Scheduled) {
524 ftpResponse = new FtpWebResponse (this, requestUri, method, keepAlive);
528 //State = RequestState.Finished;
529 //finalResponse = ftpResponse;
530 asyncResult.SetCompleted (false, ftpResponse);
532 catch (Exception e) {
533 if (!GetServicePoint ().UsesProxy)
534 State = RequestState.Error;
535 SetCompleteWithError (e);
540 FtpStatus status = GetResponseStatus ();
542 ftpResponse.UpdateStatus (status);
544 if (ftpResponse.IsFinal ()) {
545 State = RequestState.Finished;
549 asyncResult.SetCompleted (false, ftpResponse);
556 FtpStatus status = SendCommand (TypeCommand, DataType);
557 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
558 throw CreateExceptionFromResponse (status);
562 string GetRemoteFolderPath (Uri uri)
565 string local_path = Uri.UnescapeDataString (uri.LocalPath);
566 if (initial_path == null || initial_path == "/") {
569 if (local_path [0] == '/')
570 local_path = local_path.Substring (1);
572 UriBuilder initialBuilder = new UriBuilder () {
577 Uri initial = initialBuilder.Uri;
578 result = new Uri (initial, local_path).LocalPath;
581 int last = result.LastIndexOf ('/');
585 return result.Substring (0, last + 1);
588 void CWDAndSetFileName (Uri uri)
590 string remote_folder = GetRemoteFolderPath (uri);
592 if (remote_folder != null) {
593 status = SendCommand (ChangeDir, remote_folder);
594 if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
595 throw CreateExceptionFromResponse (status);
597 int last = uri.LocalPath.LastIndexOf ('/');
599 file_name = Uri.UnescapeDataString (uri.LocalPath.Substring (last + 1));
604 void ProcessMethod ()
606 ServicePoint sp = GetServicePoint ();
608 if (method != WebRequestMethods.Ftp.DownloadFile)
609 throw new NotSupportedException ("FTP+proxy only supports RETR");
611 HttpWebRequest req = (HttpWebRequest) WebRequest.Create (proxy.GetProxy (requestUri));
612 req.Address = requestUri;
613 requestState = RequestState.Finished;
614 WebResponse response = req.GetResponse ();
615 ftpResponse.Stream = new FtpDataStream (this, response.GetResponseStream (), true);
616 ftpResponse.StatusCode = FtpStatusCode.CommandOK;
619 State = RequestState.Connecting;
623 OpenControlConnection ();
624 CWDAndSetFileName (requestUri);
628 // Open data connection and receive data
629 case WebRequestMethods.Ftp.DownloadFile:
630 case WebRequestMethods.Ftp.ListDirectory:
631 case WebRequestMethods.Ftp.ListDirectoryDetails:
634 // Open data connection and send data
635 case WebRequestMethods.Ftp.AppendFile:
636 case WebRequestMethods.Ftp.UploadFile:
637 case WebRequestMethods.Ftp.UploadFileWithUniqueName:
640 // Get info from control connection
641 case WebRequestMethods.Ftp.GetFileSize:
642 case WebRequestMethods.Ftp.GetDateTimestamp:
643 case WebRequestMethods.Ftp.PrintWorkingDirectory:
644 case WebRequestMethods.Ftp.MakeDirectory:
645 case WebRequestMethods.Ftp.Rename:
646 case WebRequestMethods.Ftp.DeleteFile:
647 ProcessSimpleMethod ();
649 default: // What to do here?
650 throw new Exception (String.Format ("Support for command {0} not implemented yet", method));
656 private void CloseControlConnection () {
657 if (controlStream != null) {
658 SendCommand (QuitCommand);
659 controlStream.Close ();
660 controlStream = null;
664 internal void CloseDataConnection () {
665 if(origDataStream != null) {
666 origDataStream.Close ();
667 origDataStream = null;
671 private void CloseConnection () {
672 CloseControlConnection ();
673 CloseDataConnection ();
676 void ProcessSimpleMethod ()
678 State = RequestState.TransferInProgress;
682 if (method == WebRequestMethods.Ftp.PrintWorkingDirectory)
685 if (method == WebRequestMethods.Ftp.Rename)
686 method = RenameFromCommand;
688 status = SendCommand (method, file_name);
690 ftpResponse.Stream = Stream.Null;
692 string desc = status.StatusDescription;
695 case WebRequestMethods.Ftp.GetFileSize: {
696 if (status.StatusCode != FtpStatusCode.FileStatus)
697 throw CreateExceptionFromResponse (status);
701 for (i = 4, len = 0; i < desc.Length && Char.IsDigit (desc [i]); i++, len++)
705 throw new WebException ("Bad format for server response in " + method);
707 if (!Int64.TryParse (desc.Substring (4, len), out size))
708 throw new WebException ("Bad format for server response in " + method);
710 ftpResponse.contentLength = size;
713 case WebRequestMethods.Ftp.GetDateTimestamp:
714 if (status.StatusCode != FtpStatusCode.FileStatus)
715 throw CreateExceptionFromResponse (status);
716 ftpResponse.LastModified = DateTime.ParseExact (desc.Substring (4), "yyyyMMddHHmmss", null);
718 case WebRequestMethods.Ftp.MakeDirectory:
719 if (status.StatusCode != FtpStatusCode.PathnameCreated)
720 throw CreateExceptionFromResponse (status);
723 method = WebRequestMethods.Ftp.PrintWorkingDirectory;
725 if (status.StatusCode != FtpStatusCode.FileActionOK)
726 throw CreateExceptionFromResponse (status);
728 status = SendCommand (method);
730 if (status.StatusCode != FtpStatusCode.PathnameCreated)
731 throw CreateExceptionFromResponse (status);
733 case RenameFromCommand:
734 method = WebRequestMethods.Ftp.Rename;
735 if (status.StatusCode != FtpStatusCode.FileCommandPending)
736 throw CreateExceptionFromResponse (status);
737 // Pass an empty string if RenameTo wasn't specified
738 status = SendCommand (RenameToCommand, renameTo != null ? renameTo : String.Empty);
739 if (status.StatusCode != FtpStatusCode.FileActionOK)
740 throw CreateExceptionFromResponse (status);
742 case WebRequestMethods.Ftp.DeleteFile:
743 if (status.StatusCode != FtpStatusCode.FileActionOK) {
744 throw CreateExceptionFromResponse (status);
749 State = RequestState.Finished;
754 State = RequestState.OpeningData;
756 OpenDataConnection ();
758 State = RequestState.TransferInProgress;
759 requestStream = new FtpDataStream (this, dataStream, false);
760 asyncResult.Stream = requestStream;
765 State = RequestState.OpeningData;
767 OpenDataConnection ();
769 State = RequestState.TransferInProgress;
770 ftpResponse.Stream = new FtpDataStream (this, dataStream, true);
773 void CheckRequestStarted ()
775 if (State != RequestState.Before)
776 throw new InvalidOperationException ("There is a request currently in progress");
779 void OpenControlConnection ()
781 Exception exception = null;
783 foreach (IPAddress address in hostEntry.AddressList) {
784 sock = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
786 remoteEndPoint = new IPEndPoint (address, requestUri.Port);
788 if (!ServicePoint.CallEndPointDelegate (sock, remoteEndPoint)) {
793 sock.Connect (remoteEndPoint);
794 localEndPoint = (IPEndPoint) sock.LocalEndPoint;
796 } catch (SocketException exc) {
804 // Couldn't connect to any address
806 throw new WebException ("Unable to connect to remote server", exception,
807 WebExceptionStatus.UnknownError, ftpResponse);
809 controlStream = new NetworkStream (sock);
810 controlReader = new StreamReader (controlStream, Encoding.ASCII);
812 State = RequestState.Authenticating;
815 FtpStatus status = SendCommand ("OPTS", "utf8", "on");
816 if ((int)status.StatusCode < 200 || (int)status.StatusCode > 300)
817 dataEncoding = Encoding.Default;
819 dataEncoding = Encoding.UTF8;
821 status = SendCommand (WebRequestMethods.Ftp.PrintWorkingDirectory);
822 initial_path = GetInitialPath (status);
825 static string GetInitialPath (FtpStatus status)
827 int s = (int) status.StatusCode;
828 if (s < 200 || s > 300 || status.StatusDescription.Length <= 4)
829 throw new WebException ("Error getting current directory: " + status.StatusDescription, null,
830 WebExceptionStatus.UnknownError, null);
832 string msg = status.StatusDescription.Substring (4);
833 if (msg [0] == '"') {
834 int next_quote = msg.IndexOf ('\"', 1);
835 if (next_quote == -1)
836 throw new WebException ("Error getting current directory: PWD -> " + status.StatusDescription, null,
837 WebExceptionStatus.UnknownError, null);
839 msg = msg.Substring (1, next_quote - 1);
842 if (!msg.EndsWith ("/"))
847 // Probably we could do better having here a regex
848 Socket SetupPassiveConnection (string statusDescription, bool ipv6)
850 // Current response string
851 string response = statusDescription;
852 if (response.Length < 4)
853 throw new WebException ("Cannot open passive data connection");
855 int port = ipv6 ? GetPortV6 (response) : GetPortV4 (response);
857 if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
858 throw new WebException ("Cannot open passive data connection");
860 IPEndPoint ep = new IPEndPoint (remoteEndPoint.Address, port);
861 Socket sock = new Socket (ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
864 } catch (SocketException) {
866 throw new WebException ("Cannot open passive data connection");
872 // GetPortV4, GetPortV6, FormatAddress and FormatAddressV6 are copied from referencesource
873 // TODO: replace FtpWebRequest completely.
874 private int GetPortV4(string responseString)
876 string [] parsedList = responseString.Split(new char [] {' ', '(', ',', ')'});
878 // We need at least the status code and the port
879 if (parsedList.Length <= 7) {
880 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
883 int index = parsedList.Length-1;
884 // skip the last non-number token (e.g. terminating '.')
886 // the MS code expects \r\n here in parsedList[index],
887 // but we're stripping the EOL off earlier so the array contains
888 // an empty string here which would make Char.IsNumber throw
889 // TODO: this can be removed once we switch FtpWebRequest to referencesource
890 if (parsedList[index] == "" || !Char.IsNumber(parsedList[index], 0))
892 if (!Char.IsNumber(parsedList[index], 0))
896 int port = Convert.ToByte(parsedList[index--], NumberFormatInfo.InvariantInfo);
898 (Convert.ToByte(parsedList[index--], NumberFormatInfo.InvariantInfo) << 8);
903 private int GetPortV6(string responseString)
905 int pos1 = responseString.LastIndexOf("(");
906 int pos2 = responseString.LastIndexOf(")");
907 if (pos1 == -1 || pos2 <= pos1)
908 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
910 // addressInfo will contain a string of format "|||<tcp-port>|"
911 string addressInfo = responseString.Substring(pos1+1, pos2-pos1-1);
913 // Although RFC2428 recommends using "|" as the delimiter,
914 // It allows ASCII characters in range 33-126 inclusive.
915 // We should consider allowing the full range.
917 string [] parsedList = addressInfo.Split(new char [] {'|'});
918 if (parsedList.Length < 4)
919 throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
921 return Convert.ToInt32(parsedList[3], NumberFormatInfo.InvariantInfo);
924 private String FormatAddress(IPAddress address, int Port )
926 byte [] localAddressInBytes = address.GetAddressBytes();
928 // produces a string in FTP IPAddress/Port encoding (a1, a2, a3, a4, p1, p2), for sending as a parameter
929 // to the port command.
930 StringBuilder sb = new StringBuilder(32);
931 foreach (byte element in localAddressInBytes) {
935 sb.Append(Port / 256 );
937 sb.Append(Port % 256 );
938 return sb.ToString();
941 private string FormatAddressV6(IPAddress address, int port) {
942 StringBuilder sb = new StringBuilder(43); // based on max size of IPv6 address + port + seperators
943 String addressString = address.ToString();
945 sb.Append(addressString);
947 sb.Append(port.ToString(NumberFormatInfo.InvariantInfo));
949 return sb.ToString();
953 Exception CreateExceptionFromResponse (FtpStatus status)
955 FtpWebResponse ftpResponse = new FtpWebResponse (this, requestUri, method, status);
957 WebException exc = new WebException ("Server returned an error: " + status.StatusDescription,
958 null, WebExceptionStatus.ProtocolError, ftpResponse);
962 // Here we could also get a server error, so be cautious
963 internal void SetTransferCompleted ()
968 State = RequestState.Finished;
969 FtpStatus status = GetResponseStatus ();
970 ftpResponse.UpdateStatus (status);
975 internal void OperationCompleted ()
981 void SetCompleteWithError (Exception exc)
983 if (asyncResult != null) {
984 asyncResult.SetCompleted (false, exc);
988 Socket InitDataConnection ()
991 bool ipv6 = remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6;
994 status = SendCommand (ipv6 ? ExtendedPassiveCommand : PassiveCommand);
995 if (status.StatusCode != (ipv6 ? (FtpStatusCode)229 : FtpStatusCode.EnteringPassive)) { // FtpStatusCode doesn't contain code 229 for EPSV so we need to cast...
996 throw CreateExceptionFromResponse (status);
999 return SetupPassiveConnection (status.StatusDescription, ipv6);
1002 // Open a socket to listen the server's connection
1003 Socket sock = new Socket (remoteEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
1005 sock.Bind (new IPEndPoint (localEndPoint.Address, 0));
1006 sock.Listen (1); // We only expect a connection from server
1008 } catch (SocketException e) {
1011 throw new WebException ("Couldn't open listening socket on client", e);
1014 IPEndPoint ep = (IPEndPoint) sock.LocalEndPoint;
1016 var portParam = ipv6 ? FormatAddressV6 (ep.Address, ep.Port) : FormatAddress (ep.Address, ep.Port);
1018 status = SendCommand (ipv6 ? ExtendedPortCommand : PortCommand, portParam);
1020 if (status.StatusCode != FtpStatusCode.CommandOK) {
1022 throw (CreateExceptionFromResponse (status));
1028 void OpenDataConnection ()
1032 Socket s = InitDataConnection ();
1034 // Handle content offset
1036 status = SendCommand (RestCommand, offset.ToString ());
1037 if (status.StatusCode != FtpStatusCode.FileCommandPending)
1038 throw CreateExceptionFromResponse (status);
1041 if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails &&
1042 method != WebRequestMethods.Ftp.UploadFileWithUniqueName) {
1043 status = SendCommand (method, file_name);
1045 status = SendCommand (method);
1048 if (status.StatusCode != FtpStatusCode.OpeningData && status.StatusCode != FtpStatusCode.DataAlreadyOpen)
1049 throw CreateExceptionFromResponse (status);
1052 origDataStream = new NetworkStream (s, true);
1053 dataStream = origDataStream;
1055 ChangeToSSLSocket (ref dataStream);
1059 // Active connection (use Socket.Blocking to true)
1060 Socket incoming = null;
1062 incoming = s.Accept ();
1064 catch (SocketException) {
1066 if (incoming != null)
1069 throw new ProtocolViolationException ("Server commited a protocol violation.");
1073 origDataStream = new NetworkStream (incoming, true);
1074 dataStream = origDataStream;
1076 ChangeToSSLSocket (ref dataStream);
1079 ftpResponse.UpdateStatus (status);
1082 void Authenticate ()
1084 string username = null;
1085 string password = null;
1086 string domain = null;
1088 if (credentials != null) {
1089 username = credentials.UserName;
1090 password = credentials.Password;
1091 domain = credentials.Domain;
1094 if (username == null)
1095 username = "anonymous";
1096 if (password == null)
1097 password = "@anonymous";
1098 if (!string.IsNullOrEmpty (domain))
1099 username = domain + '\\' + username;
1101 // Connect to server and get banner message
1102 FtpStatus status = GetResponseStatus ();
1103 ftpResponse.BannerMessage = status.StatusDescription;
1106 InitiateSecureConnection (ref controlStream);
1107 controlReader = new StreamReader (controlStream, Encoding.ASCII);
1108 status = SendCommand ("PBSZ", "0");
1109 int st = (int) status.StatusCode;
1110 if (st < 200 || st >= 300)
1111 throw CreateExceptionFromResponse (status);
1112 // TODO: what if "PROT P" is denied by the server? What does MS do?
1113 status = SendCommand ("PROT", "P");
1114 st = (int) status.StatusCode;
1115 if (st < 200 || st >= 300)
1116 throw CreateExceptionFromResponse (status);
1118 status = new FtpStatus (FtpStatusCode.SendUserCommand, "");
1121 if (status.StatusCode != FtpStatusCode.SendUserCommand)
1122 throw CreateExceptionFromResponse (status);
1124 status = SendCommand (UserCommand, username);
1126 switch (status.StatusCode) {
1127 case FtpStatusCode.SendPasswordCommand:
1128 status = SendCommand (PasswordCommand, password);
1129 if (status.StatusCode != FtpStatusCode.LoggedInProceed)
1130 throw CreateExceptionFromResponse (status);
1132 case FtpStatusCode.LoggedInProceed:
1135 throw CreateExceptionFromResponse (status);
1138 ftpResponse.WelcomeMessage = status.StatusDescription;
1139 ftpResponse.UpdateStatus (status);
1142 FtpStatus SendCommand (string command, params string [] parameters) {
1143 return SendCommand (true, command, parameters);
1146 FtpStatus SendCommand (bool waitResponse, string command, params string [] parameters)
1149 string commandString = command;
1150 if (parameters.Length > 0)
1151 commandString += " " + String.Join (" ", parameters);
1153 commandString += EOL;
1154 cmd = dataEncoding.GetBytes (commandString);
1156 controlStream.Write (cmd, 0, cmd.Length);
1157 } catch (IOException) {
1158 //controlStream.Close ();
1159 return new FtpStatus(FtpStatusCode.ServiceNotAvailable, "Write failed");
1165 FtpStatus result = GetResponseStatus ();
1166 if (ftpResponse != null)
1167 ftpResponse.UpdateStatus (result);
1171 internal static FtpStatus ServiceNotAvailable ()
1173 return new FtpStatus (FtpStatusCode.ServiceNotAvailable, Locale.GetText ("Invalid response from server"));
1176 internal FtpStatus GetResponseStatus ()
1179 string response = null;
1182 response = controlReader.ReadLine ();
1183 } catch (IOException) {
1186 if (response == null || response.Length < 3)
1187 return ServiceNotAvailable ();
1190 if (!Int32.TryParse (response.Substring (0, 3), out code))
1191 return ServiceNotAvailable ();
1193 if (response.Length > 3 && response [3] == '-'){
1195 string find = code.ToString() + ' ';
1199 line = controlReader.ReadLine();
1200 } catch (IOException) {
1203 return ServiceNotAvailable ();
1205 response += Environment.NewLine + line;
1207 if (line.StartsWith(find, StringComparison.Ordinal))
1211 return new FtpStatus ((FtpStatusCode) code, response);
1215 private void InitiateSecureConnection (ref Stream stream) {
1216 FtpStatus status = SendCommand (AuthCommand, "TLS");
1217 if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession)
1218 throw CreateExceptionFromResponse (status);
1220 ChangeToSSLSocket (ref stream);
1223 internal bool ChangeToSSLSocket (ref Stream stream) {
1225 var provider = MonoTlsProviderFactory.GetProviderInternal ();
1226 var settings = MSI.MonoTlsSettings.CopyDefaultSettings ();
1227 settings.UseServicePointManagerCallback = true;
1228 var sslStream = provider.CreateSslStream (stream, true, settings);
1229 sslStream.AuthenticateAsClient (requestUri.Host, null, SslProtocols.Default, false);
1230 stream = sslStream.AuthenticatedStream;
1233 throw new NotImplementedException ();
1237 bool InFinalState () {
1238 return (State == RequestState.Aborted || State == RequestState.Error || State == RequestState.Finished);
1241 bool InProgress () {
1242 return (State != RequestState.Before && !InFinalState ());
1245 internal void CheckIfAborted () {
1246 if (State == RequestState.Aborted)
1247 throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
1250 void CheckFinalState () {
1251 if (InFinalState ())
1252 throw new InvalidOperationException ("Cannot change final state");