FtpWebRequestTest.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. //
  2. // FtpWebRequestTest.cs - NUnit Test Cases for System.Net.FtpWebRequest
  3. //
  4. // Authors:
  5. // Carlos Alberto Cortez <[email protected]>
  6. // Gonzalo Paniagua Javier <[email protected]>
  7. //
  8. // Copyright (c) 2006,2007,2008 Novell, Inc. (http://www.novell.com)
  9. //
  10. using NUnit.Framework;
  11. using System;
  12. using System.Collections.Generic;
  13. using System.IO;
  14. using System.Net;
  15. using System.Net.Sockets;
  16. using System.Text;
  17. using System.Threading;
  18. namespace MonoTests.System.Net
  19. {
  20. [TestFixture]
  21. public class FtpWebRequestTest
  22. {
  23. FtpWebRequest defaultRequest;
  24. private string _tempDirectory;
  25. private string _tempFile;
  26. [SetUp]
  27. public void SetUp ()
  28. {
  29. _tempDirectory = Path.Combine (Path.GetTempPath (), "MonoTests.System.Net.FileWebRequestTest");
  30. _tempFile = Path.Combine (_tempDirectory, "FtpWebRequestTest.tmp");
  31. if (!Directory.Exists (_tempDirectory)) {
  32. Directory.CreateDirectory (_tempDirectory);
  33. } else {
  34. // ensure no files are left over from previous runs
  35. string [] files = Directory.GetFiles (_tempDirectory, "*");
  36. foreach (string file in files)
  37. File.Delete (file);
  38. }
  39. }
  40. [TearDown]
  41. public void TearDown ()
  42. {
  43. if (Directory.Exists (_tempDirectory))
  44. Directory.Delete (_tempDirectory, true);
  45. }
  46. [TestFixtureSetUp]
  47. public void Init ()
  48. {
  49. defaultRequest = (FtpWebRequest) WebRequest.Create ("ftp://www.contoso.com");
  50. }
  51. [Test]
  52. public void ContentLength ()
  53. {
  54. try {
  55. long l = defaultRequest.ContentLength;
  56. } catch (NotSupportedException) {
  57. Assert.Fail ("#1"); // Not overriden
  58. }
  59. try {
  60. defaultRequest.ContentLength = 2;
  61. } catch (NotSupportedException) {
  62. Assert.Fail ("#2"); // Not overriden
  63. }
  64. }
  65. [Test]
  66. public void ContentType ()
  67. {
  68. try {
  69. string t = defaultRequest.ContentType;
  70. Assert.Fail ("#1");
  71. } catch (NotSupportedException) {
  72. }
  73. try {
  74. defaultRequest.ContentType = String.Empty;
  75. Assert.Fail ("#2");
  76. } catch (NotSupportedException) {
  77. }
  78. }
  79. [Test]
  80. public void ContentOffset ()
  81. {
  82. try {
  83. defaultRequest.ContentOffset = -2;
  84. Assert.Fail ("#1");
  85. } catch (ArgumentOutOfRangeException) {
  86. }
  87. }
  88. [Test]
  89. public void Credentials ()
  90. {
  91. try {
  92. defaultRequest.Credentials = null;
  93. Assert.Fail ("#1");
  94. } catch (ArgumentNullException) {
  95. }
  96. }
  97. [Test]
  98. public void Method ()
  99. {
  100. try {
  101. defaultRequest.Method = null;
  102. Assert.Fail ("#1");
  103. } catch (ArgumentNullException) {
  104. }
  105. try {
  106. defaultRequest.Method = String.Empty;
  107. Assert.Fail ("#2");
  108. } catch (ArgumentException) {
  109. }
  110. try {
  111. defaultRequest.Method = "WrongValue";
  112. Assert.Fail ("#3");
  113. } catch (ArgumentException) {
  114. }
  115. }
  116. [Test]
  117. public void PreAuthenticate ()
  118. {
  119. try {
  120. bool p = defaultRequest.PreAuthenticate;
  121. Assert.Fail ("#1");
  122. } catch (NotSupportedException) {
  123. }
  124. try {
  125. defaultRequest.PreAuthenticate = true;
  126. } catch (NotSupportedException) {
  127. }
  128. }
  129. [Test]
  130. public void ReadWriteTimeout ()
  131. {
  132. try {
  133. defaultRequest.ReadWriteTimeout = -2;
  134. Assert.Fail ("#2");
  135. } catch (ArgumentOutOfRangeException) {
  136. }
  137. }
  138. [Test]
  139. public void Timeout ()
  140. {
  141. try {
  142. defaultRequest.Timeout = -2;
  143. Assert.Fail ("#2");
  144. } catch (ArgumentOutOfRangeException) {
  145. }
  146. }
  147. [Test]
  148. public void DefaultValues ()
  149. {
  150. FtpWebRequest request = (FtpWebRequest) WebRequest.Create ("ftp://www.contoso.com");
  151. Assert.AreEqual (0, request.ContentOffset, "ContentOffset");
  152. Assert.AreEqual (false, request.EnableSsl, "EnableSsl");
  153. // FIXME: Disabled this one by now. KeepAlive is not well supported.
  154. // Assert.AreEqual (true, request.KeepAlive, "KeepAlive");
  155. Assert.AreEqual (WebRequestMethods.Ftp.DownloadFile, request.Method, "#1");
  156. Assert.AreEqual (300000, request.ReadWriteTimeout, "ReadWriteTimeout");
  157. Assert.IsNull (request.RenameTo, "RenameTo");
  158. Assert.AreEqual (true, request.UseBinary, "UseBinary");
  159. Assert.AreEqual (100000, request.Timeout, "Timeout");
  160. Assert.AreEqual (true, request.UsePassive, "UsePassive");
  161. }
  162. [Test]
  163. public void RenameTo ()
  164. {
  165. try {
  166. defaultRequest.RenameTo = null;
  167. Assert.Fail ("#1");
  168. } catch (ArgumentException) {
  169. }
  170. try {
  171. defaultRequest.RenameTo = String.Empty;
  172. Assert.Fail ("#2");
  173. } catch (ArgumentException) {
  174. }
  175. }
  176. [Test]
  177. public void UploadFile1 ()
  178. {
  179. ServerPut sp = new ServerPut ();
  180. sp.Start ();
  181. string uri = String.Format ("ftp://{0}:{1}/uploads/file.txt", sp.IPAddress, sp.Port);
  182. try {
  183. FtpWebRequest ftp = (FtpWebRequest) WebRequest.Create (uri);
  184. ftp.KeepAlive = false;
  185. ftp.Timeout = 5000;
  186. ftp.Method = WebRequestMethods.Ftp.UploadFile;
  187. ftp.ContentLength = 10;
  188. ftp.UseBinary = true;
  189. Stream stream = ftp.GetRequestStream ();
  190. for (int i = 0; i < 10; i++)
  191. stream.WriteByte ((byte)i);
  192. stream.Close ();
  193. FtpWebResponse response = (FtpWebResponse) ftp.GetResponse ();
  194. Assert.IsTrue ((int) response.StatusCode >= 200 && (int) response.StatusCode < 300, "UP#01");
  195. Assert.AreEqual (10, sp.result.Count, "UP#02");
  196. response.Close ();
  197. } catch (Exception) {
  198. if (!String.IsNullOrEmpty (sp.Where))
  199. throw new Exception (sp.Where);
  200. throw;
  201. } finally {
  202. sp.Stop ();
  203. }
  204. }
  205. [Test]
  206. public void UploadFile_WebClient ()
  207. {
  208. ServerPut sp = new ServerPut ();
  209. File.WriteAllText (_tempFile, "0123456789");
  210. sp.Start ();
  211. using (WebClient m_WebClient = new WebClient())
  212. {
  213. string uri = String.Format ("ftp://{0}:{1}/uploads/file.txt", sp.IPAddress, sp.Port);
  214. m_WebClient.UploadFile(uri, _tempFile);
  215. }
  216. Assert.AreEqual (10, sp.result.Count, "WebClient/Ftp#01");
  217. sp.Stop ();
  218. }
  219. [Test]
  220. public void DownloadFile1 ()
  221. {
  222. DownloadFile (new ServerDownload ());
  223. }
  224. void DownloadFile (ServerDownload sp)
  225. {
  226. sp.Start ();
  227. string uri = String.Format ("ftp://{0}:{1}/file.txt", sp.IPAddress, sp.Port);
  228. try {
  229. FtpWebRequest ftp = (FtpWebRequest) WebRequest.Create (uri);
  230. ftp.KeepAlive = false;
  231. ftp.Timeout = 5000;
  232. ftp.Method = WebRequestMethods.Ftp.DownloadFile;
  233. ftp.UseBinary = true;
  234. FtpWebResponse response = (FtpWebResponse) ftp.GetResponse ();
  235. Assert.IsTrue ((int) response.StatusCode >= 100 && (int) response.StatusCode < 200, "DL#01");
  236. using (Stream st = response.GetResponseStream ()) {
  237. }
  238. // This should be "220 Bye" or similar (no KeepAlive)
  239. Assert.IsTrue ((int) response.StatusCode >= 200 && (int) response.StatusCode < 300, "DL#02");
  240. response.Close ();
  241. } catch (Exception) {
  242. if (!String.IsNullOrEmpty (sp.Where))
  243. throw new Exception (sp.Where);
  244. throw;
  245. } finally {
  246. sp.Stop ();
  247. }
  248. }
  249. [Test]
  250. public void DownloadFile2 ()
  251. {
  252. // Some embedded FTP servers in Industrial Automation Hardware report
  253. // the PWD using backslashes, but allow forward slashes for CWD.
  254. DownloadFile (new ServerDownload (@"\Users\someuser", "/Users/someuser/"));
  255. }
  256. [Test]
  257. public void DeleteFile1 ()
  258. {
  259. ServerDeleteFile sp = new ServerDeleteFile ();
  260. sp.Start ();
  261. string uri = String.Format ("ftp://{0}:{1}/file.txt", sp.IPAddress, sp.Port);
  262. try {
  263. FtpWebRequest ftp = (FtpWebRequest) WebRequest.Create (uri);
  264. Console.WriteLine (ftp.RequestUri);
  265. ftp.KeepAlive = false;
  266. ftp.Timeout = 5000;
  267. ftp.Method = WebRequestMethods.Ftp.DeleteFile;
  268. ftp.UseBinary = true;
  269. FtpWebResponse response = (FtpWebResponse) ftp.GetResponse ();
  270. Assert.IsTrue ((int) response.StatusCode >= 200 && (int) response.StatusCode < 300, "DF#01");
  271. response.Close ();
  272. } catch (Exception e) {
  273. Console.WriteLine (e);
  274. if (!String.IsNullOrEmpty (sp.Where))
  275. throw new Exception (sp.Where);
  276. throw;
  277. } finally {
  278. sp.Stop ();
  279. }
  280. }
  281. [Test]
  282. public void ListDirectory1 ()
  283. {
  284. ServerListDirectory sp = new ServerListDirectory ();
  285. sp.Start ();
  286. string uri = String.Format ("ftp://{0}:{1}/somedir/", sp.IPAddress, sp.Port);
  287. try {
  288. FtpWebRequest ftp = (FtpWebRequest) WebRequest.Create (uri);
  289. Console.WriteLine (ftp.RequestUri);
  290. ftp.KeepAlive = false;
  291. ftp.Timeout = 5000;
  292. ftp.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
  293. ftp.UseBinary = true;
  294. using (FtpWebResponse response = (FtpWebResponse) ftp.GetResponse ()) {
  295. StreamReader reader = new StreamReader (response.GetResponseStream ());
  296. string result = reader.ReadToEnd ();
  297. Assert.IsTrue ((int) response.StatusCode >= 200 && (int) response.StatusCode < 300, "DF#01");
  298. }
  299. } catch (Exception e) {
  300. Console.WriteLine (e);
  301. if (!String.IsNullOrEmpty (sp.Where))
  302. throw new Exception (sp.Where);
  303. throw;
  304. } finally {
  305. sp.Stop ();
  306. }
  307. }
  308. class ServerListDirectory : FtpServer {
  309. protected override void Run ()
  310. {
  311. Socket client = control.Accept ();
  312. NetworkStream ns = new NetworkStream (client, false);
  313. StreamWriter writer = new StreamWriter (ns, Encoding.ASCII);
  314. StreamReader reader = new StreamReader (ns, Encoding.UTF8);
  315. if (!DoAnonymousLogin (writer, reader)) {
  316. client.Close ();
  317. return;
  318. }
  319. if (!DoInitialDialog (writer, reader, "/home/someuser", "/home/someuser/somedir/")) {
  320. client.Close ();
  321. return;
  322. }
  323. string str = reader.ReadLine ();
  324. if (str != "PASV") {
  325. Where = "PASV";
  326. client.Close ();
  327. return;
  328. }
  329. IPEndPoint end_data = (IPEndPoint) data.LocalEndPoint;
  330. byte [] addr_bytes = end_data.Address.GetAddressBytes ();
  331. byte [] port = new byte [2];
  332. port[0] = (byte) ((end_data.Port >> 8) & 255);
  333. port[1] = (byte) (end_data.Port & 255);
  334. StringBuilder sb = new StringBuilder ("227 Passive (");
  335. foreach (byte b in addr_bytes) {
  336. sb.AppendFormat ("{0},", b);
  337. }
  338. sb.AppendFormat ("{0},", port [0]);
  339. sb.AppendFormat ("{0})", port [1]);
  340. writer.WriteLine (sb.ToString ());
  341. writer.Flush ();
  342. str = reader.ReadLine ();
  343. if (str != "LIST") {
  344. Where = "LIST - '" + str + "'";
  345. client.Close ();
  346. return;
  347. }
  348. writer.WriteLine ("150 Here comes the directory listing");
  349. writer.Flush ();
  350. Socket data_cnc = data.Accept ();
  351. byte [] dontcare = Encoding.ASCII.GetBytes ("drwxr-xr-x 2 ftp ftp 4096 Oct 27 20:17 tests");
  352. data_cnc.Send (dontcare, 1, SocketFlags.None);
  353. data_cnc.Close ();
  354. writer.WriteLine ("226 Directory send Ok");
  355. writer.Flush ();
  356. if (!EndConversation (writer, reader)) {
  357. client.Close ();
  358. return;
  359. }
  360. client.Close ();
  361. }
  362. }
  363. class ServerDeleteFile : FtpServer {
  364. protected override void Run ()
  365. {
  366. Socket client = control.Accept ();
  367. NetworkStream ns = new NetworkStream (client, false);
  368. StreamWriter writer = new StreamWriter (ns, Encoding.ASCII);
  369. StreamReader reader = new StreamReader (ns, Encoding.UTF8);
  370. if (!DoAnonymousLogin (writer, reader)) {
  371. client.Close ();
  372. return;
  373. }
  374. if (!DoInitialDialog (writer, reader, "/home/someuser", "/home/someuser/")) {
  375. client.Close ();
  376. return;
  377. }
  378. string str = reader.ReadLine ();
  379. if (str.Trim () != "DELE file.txt") {
  380. Where = "DELE - " + str;
  381. client.Close ();
  382. return;
  383. }
  384. writer.WriteLine ("250 Delete operation successful");
  385. writer.Flush ();
  386. if (!EndConversation (writer, reader)) {
  387. client.Close ();
  388. return;
  389. }
  390. client.Close ();
  391. }
  392. }
  393. class ServerDownload : FtpServer {
  394. string Pwd, Cwd;
  395. public ServerDownload ()
  396. : this (null, null)
  397. {
  398. }
  399. public ServerDownload (string pwd, string cwd)
  400. {
  401. Pwd = pwd ?? "/home/someuser";
  402. Cwd = cwd ?? "/home/someuser/";
  403. }
  404. protected override void Run ()
  405. {
  406. Socket client = control.Accept ();
  407. NetworkStream ns = new NetworkStream (client, false);
  408. StreamWriter writer = new StreamWriter (ns, Encoding.ASCII);
  409. StreamReader reader = new StreamReader (ns, Encoding.UTF8);
  410. if (!DoAnonymousLogin (writer, reader)) {
  411. client.Close ();
  412. return;
  413. }
  414. if (!DoInitialDialog (writer, reader, Pwd, Cwd)) {
  415. client.Close ();
  416. return;
  417. }
  418. string str = reader.ReadLine ();
  419. if (str != "PASV") {
  420. Where = "PASV";
  421. client.Close ();
  422. return;
  423. }
  424. IPEndPoint end_data = (IPEndPoint) data.LocalEndPoint;
  425. byte [] addr_bytes = end_data.Address.GetAddressBytes ();
  426. byte [] port = new byte [2];
  427. port[0] = (byte) ((end_data.Port >> 8) & 255);
  428. port[1] = (byte) (end_data.Port & 255);
  429. StringBuilder sb = new StringBuilder ("227 Passive (");
  430. foreach (byte b in addr_bytes) {
  431. sb.AppendFormat ("{0},", b);
  432. }
  433. sb.AppendFormat ("{0},", port [0]);
  434. sb.AppendFormat ("{0})", port [1]);
  435. writer.WriteLine (sb.ToString ());
  436. writer.Flush ();
  437. str = reader.ReadLine ();
  438. if (str != "RETR file.txt") {
  439. Where = "RETR - " + str;
  440. client.Close ();
  441. return;
  442. }
  443. writer.WriteLine ("150 Opening BINARY mode data connection for blah (n bytes)");
  444. writer.Flush ();
  445. Socket data_cnc = data.Accept ();
  446. byte [] dontcare = new byte [1];
  447. data_cnc.Receive (dontcare, 1, SocketFlags.None);
  448. data_cnc.Close ();
  449. writer.WriteLine ("226 File send Ok");
  450. writer.Flush ();
  451. if (!EndConversation (writer, reader)) {
  452. client.Close ();
  453. return;
  454. }
  455. client.Close ();
  456. }
  457. }
  458. class ServerPut : FtpServer {
  459. public List<byte> result = new List<byte> ();
  460. protected override void Run ()
  461. {
  462. Socket client = control.Accept ();
  463. NetworkStream ns = new NetworkStream (client, false);
  464. StreamWriter writer = new StreamWriter (ns, Encoding.ASCII);
  465. StreamReader reader = new StreamReader (ns, Encoding.UTF8);
  466. if (!DoAnonymousLogin (writer, reader)) {
  467. client.Close ();
  468. return;
  469. }
  470. if (!DoInitialDialog (writer, reader, "/home/someuser", "/home/someuser/uploads/")) {
  471. client.Close ();
  472. return;
  473. }
  474. string str = reader.ReadLine ();
  475. if (str != "PASV") {
  476. Where = "PASV";
  477. client.Close ();
  478. return;
  479. }
  480. IPEndPoint end_data = (IPEndPoint) data.LocalEndPoint;
  481. byte [] addr_bytes = end_data.Address.GetAddressBytes ();
  482. byte [] port = new byte [2];
  483. port[0] = (byte) ((end_data.Port >> 8) & 255);
  484. port[1] = (byte) (end_data.Port & 255);
  485. StringBuilder sb = new StringBuilder ("227 Passive (");
  486. foreach (byte b in addr_bytes) {
  487. sb.AppendFormat ("{0},", b);
  488. }
  489. sb.AppendFormat ("{0},", port [0]);
  490. sb.AppendFormat ("{0})", port [1]);
  491. writer.WriteLine (sb.ToString ());
  492. writer.Flush ();
  493. str = reader.ReadLine ();
  494. if (str != "STOR file.txt") {
  495. Where = "STOR - " + str;
  496. client.Close ();
  497. return;
  498. }
  499. writer.WriteLine ("150 Ok to send data");
  500. writer.Flush ();
  501. Socket data_cnc = data.Accept ();
  502. var datastr = new NetworkStream (data_cnc, false);
  503. int ch;
  504. while ((ch = datastr.ReadByte ()) != -1){
  505. result.Add ((byte)ch);
  506. }
  507. data_cnc.Close ();
  508. writer.WriteLine ("226 File received Ok");
  509. writer.Flush ();
  510. if (!EndConversation (writer, reader)) {
  511. client.Close ();
  512. return;
  513. }
  514. client.Close ();
  515. }
  516. }
  517. abstract class FtpServer {
  518. protected Socket control;
  519. protected Socket data;
  520. protected ManualResetEvent evt;
  521. public string Where = "";
  522. public FtpServer ()
  523. {
  524. control = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  525. control.Bind (new IPEndPoint (IPAddress.Loopback, 0));
  526. control.Listen (1);
  527. data = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  528. data.Bind (new IPEndPoint (IPAddress.Loopback, 0));
  529. data.Listen (1);
  530. }
  531. public void Start ()
  532. {
  533. evt = new ManualResetEvent (false);
  534. Thread th = new Thread (new ThreadStart (Run));
  535. th.Start ();
  536. }
  537. public void Stop ()
  538. {
  539. evt.Set ();
  540. data.Close ();
  541. control.Close ();
  542. }
  543. // PWD, CWD and TYPE I (type could be moved out of here)
  544. protected bool DoInitialDialog (StreamWriter writer, StreamReader reader, string pwd, string cwd)
  545. {
  546. string str = reader.ReadLine ();
  547. if (!str.StartsWith ("OPTS utf8 on")) {
  548. Where = "OPTS utf8 - " + str;
  549. return false;
  550. }
  551. writer.WriteLine ("200 Always in UTF8 mode"); // vsftpd
  552. writer.Flush ();
  553. str = reader.ReadLine ();
  554. if (!str.StartsWith ("PWD")) {
  555. Where = "PWD - " + str;
  556. return false;
  557. }
  558. writer.WriteLine ("257 \"{0}\"", pwd);
  559. writer.Flush ();
  560. str = reader.ReadLine ();
  561. if (str != ("CWD " + cwd)) {
  562. Where = "CWD - " + str;
  563. return false;
  564. }
  565. writer.WriteLine ("250 Directory changed");
  566. writer.Flush ();
  567. str = reader.ReadLine ();
  568. if (str != ("TYPE I")) {
  569. Where = "TYPE - " + str;
  570. return false;
  571. }
  572. writer.WriteLine ("200 Switching to binary mode");
  573. writer.Flush ();
  574. return true;
  575. }
  576. protected bool EndConversation (StreamWriter writer, StreamReader reader)
  577. {
  578. string str = reader.ReadLine ();
  579. if (str != "QUIT") {
  580. Where = "QUIT";
  581. return false;
  582. }
  583. writer.WriteLine ("220 Bye");
  584. writer.Flush ();
  585. Thread.Sleep (250);
  586. return true;
  587. }
  588. protected bool DoAnonymousLogin (StreamWriter writer, StreamReader reader)
  589. {
  590. writer.WriteLine ("220 Welcome to the jungle");
  591. writer.Flush ();
  592. string str = reader.ReadLine ();
  593. if (!str.StartsWith ("USER ")) {
  594. Where = "USER";
  595. return false;
  596. }
  597. writer.WriteLine ("331 Say 'Mellon'");
  598. writer.Flush ();
  599. str = reader.ReadLine ();
  600. if (!str.StartsWith ("PASS ")) {
  601. Where = "PASS";
  602. return false;
  603. }
  604. writer.WriteLine ("230 Logged in");
  605. writer.Flush ();
  606. return true;
  607. }
  608. public IPAddress IPAddress {
  609. get { return ((IPEndPoint) control.LocalEndPoint).Address; }
  610. }
  611. public int Port {
  612. get { return ((IPEndPoint) control.LocalEndPoint).Port; }
  613. }
  614. protected abstract void Run ();
  615. }
  616. }
  617. }