FtpWebRequest.cs 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  1. //
  2. // System.Net.FtpWebRequest.cs
  3. //
  4. // Authors:
  5. // Carlos Alberto Cortez ([email protected])
  6. //
  7. // (c) Copyright 2006 Novell, Inc. (http://www.novell.com)
  8. //
  9. using System;
  10. using System.IO;
  11. using System.Net.Sockets;
  12. using System.Text;
  13. using System.Threading;
  14. using System.Net.Cache;
  15. using System.Security.Cryptography.X509Certificates;
  16. using System.Net;
  17. using System.Net.Security;
  18. using System.Security.Authentication;
  19. namespace System.Net
  20. {
  21. public sealed class FtpWebRequest : WebRequest
  22. {
  23. Uri requestUri;
  24. string file_name; // By now, used for upload
  25. ServicePoint servicePoint;
  26. Stream origDataStream;
  27. Stream dataStream;
  28. Stream controlStream;
  29. StreamReader controlReader;
  30. NetworkCredential credentials;
  31. IPHostEntry hostEntry;
  32. IPEndPoint localEndPoint;
  33. IWebProxy proxy;
  34. int timeout = 100000;
  35. int rwTimeout = 300000;
  36. long offset = 0;
  37. bool binary = true;
  38. bool enableSsl = false;
  39. bool usePassive = true;
  40. bool keepAlive = false;
  41. string method = WebRequestMethods.Ftp.DownloadFile;
  42. string renameTo;
  43. object locker = new object ();
  44. RequestState requestState = RequestState.Before;
  45. FtpAsyncResult asyncResult;
  46. FtpWebResponse ftpResponse;
  47. Stream requestStream;
  48. string initial_path;
  49. const string ChangeDir = "CWD";
  50. const string UserCommand = "USER";
  51. const string PasswordCommand = "PASS";
  52. const string TypeCommand = "TYPE";
  53. const string PassiveCommand = "PASV";
  54. const string PortCommand = "PORT";
  55. const string AbortCommand = "ABOR";
  56. const string AuthCommand = "AUTH";
  57. const string RestCommand = "REST";
  58. const string RenameFromCommand = "RNFR";
  59. const string RenameToCommand = "RNTO";
  60. const string QuitCommand = "QUIT";
  61. const string EOL = "\r\n"; // Special end of line
  62. enum RequestState
  63. {
  64. Before,
  65. Scheduled,
  66. Connecting,
  67. Authenticating,
  68. OpeningData,
  69. TransferInProgress,
  70. Finished,
  71. Aborted,
  72. Error
  73. }
  74. // sorted commands
  75. static readonly string [] supportedCommands = new string [] {
  76. WebRequestMethods.Ftp.AppendFile, // APPE
  77. WebRequestMethods.Ftp.DeleteFile, // DELE
  78. WebRequestMethods.Ftp.ListDirectoryDetails, // LIST
  79. WebRequestMethods.Ftp.GetDateTimestamp, // MDTM
  80. WebRequestMethods.Ftp.MakeDirectory, // MKD
  81. WebRequestMethods.Ftp.ListDirectory, // NLST
  82. WebRequestMethods.Ftp.PrintWorkingDirectory, // PWD
  83. WebRequestMethods.Ftp.Rename, // RENAME
  84. WebRequestMethods.Ftp.DownloadFile, // RETR
  85. WebRequestMethods.Ftp.RemoveDirectory, // RMD
  86. WebRequestMethods.Ftp.GetFileSize, // SIZE
  87. WebRequestMethods.Ftp.UploadFile, // STOR
  88. WebRequestMethods.Ftp.UploadFileWithUniqueName // STUR
  89. };
  90. internal FtpWebRequest (Uri uri)
  91. {
  92. this.requestUri = uri;
  93. this.proxy = GlobalProxySelection.Select;
  94. }
  95. static Exception GetMustImplement ()
  96. {
  97. return new NotImplementedException ();
  98. }
  99. [MonoTODO]
  100. public X509CertificateCollection ClientCertificates
  101. {
  102. get {
  103. throw GetMustImplement ();
  104. }
  105. set {
  106. throw GetMustImplement ();
  107. }
  108. }
  109. [MonoTODO]
  110. public override string ConnectionGroupName
  111. {
  112. get {
  113. throw GetMustImplement ();
  114. }
  115. set {
  116. throw GetMustImplement ();
  117. }
  118. }
  119. public override string ContentType {
  120. get {
  121. throw new NotSupportedException ();
  122. }
  123. set {
  124. throw new NotSupportedException ();
  125. }
  126. }
  127. public override long ContentLength {
  128. get {
  129. return 0;
  130. }
  131. set {
  132. // DO nothing
  133. }
  134. }
  135. public long ContentOffset {
  136. get {
  137. return offset;
  138. }
  139. set {
  140. CheckRequestStarted ();
  141. if (value < 0)
  142. throw new ArgumentOutOfRangeException ();
  143. offset = value;
  144. }
  145. }
  146. public override ICredentials Credentials {
  147. get {
  148. return credentials;
  149. }
  150. set {
  151. CheckRequestStarted ();
  152. if (value == null)
  153. throw new ArgumentNullException ();
  154. if (!(value is NetworkCredential))
  155. throw new ArgumentException ();
  156. credentials = value as NetworkCredential;
  157. }
  158. }
  159. #if !NET_2_1
  160. [MonoTODO]
  161. public static new RequestCachePolicy DefaultCachePolicy
  162. {
  163. get {
  164. throw GetMustImplement ();
  165. }
  166. set {
  167. throw GetMustImplement ();
  168. }
  169. }
  170. #endif
  171. public bool EnableSsl {
  172. get {
  173. return enableSsl;
  174. }
  175. set {
  176. CheckRequestStarted ();
  177. enableSsl = value;
  178. }
  179. }
  180. [MonoTODO]
  181. public override WebHeaderCollection Headers
  182. {
  183. get {
  184. throw GetMustImplement ();
  185. }
  186. set {
  187. throw GetMustImplement ();
  188. }
  189. }
  190. [MonoTODO ("We don't support KeepAlive = true")]
  191. public bool KeepAlive {
  192. get {
  193. return keepAlive;
  194. }
  195. set {
  196. CheckRequestStarted ();
  197. //keepAlive = value;
  198. }
  199. }
  200. public override string Method {
  201. get {
  202. return method;
  203. }
  204. set {
  205. CheckRequestStarted ();
  206. if (value == null)
  207. throw new ArgumentNullException ("Method string cannot be null");
  208. if (value.Length == 0 || Array.BinarySearch (supportedCommands, value) < 0)
  209. throw new ArgumentException ("Method not supported", "value");
  210. method = value;
  211. }
  212. }
  213. public override bool PreAuthenticate {
  214. get {
  215. throw new NotSupportedException ();
  216. }
  217. set {
  218. throw new NotSupportedException ();
  219. }
  220. }
  221. public override IWebProxy Proxy {
  222. get {
  223. return proxy;
  224. }
  225. set {
  226. CheckRequestStarted ();
  227. proxy = value;
  228. }
  229. }
  230. public int ReadWriteTimeout {
  231. get {
  232. return rwTimeout;
  233. }
  234. set {
  235. CheckRequestStarted ();
  236. if (value < - 1)
  237. throw new ArgumentOutOfRangeException ();
  238. else
  239. rwTimeout = value;
  240. }
  241. }
  242. public string RenameTo {
  243. get {
  244. return renameTo;
  245. }
  246. set {
  247. CheckRequestStarted ();
  248. if (value == null || value.Length == 0)
  249. throw new ArgumentException ("RenameTo value can't be null or empty", "RenameTo");
  250. renameTo = value;
  251. }
  252. }
  253. public override Uri RequestUri {
  254. get {
  255. return requestUri;
  256. }
  257. }
  258. public ServicePoint ServicePoint {
  259. get {
  260. return GetServicePoint ();
  261. }
  262. }
  263. public bool UsePassive {
  264. get {
  265. return usePassive;
  266. }
  267. set {
  268. CheckRequestStarted ();
  269. usePassive = value;
  270. }
  271. }
  272. [MonoTODO]
  273. public override bool UseDefaultCredentials
  274. {
  275. get {
  276. throw GetMustImplement ();
  277. }
  278. set {
  279. throw GetMustImplement ();
  280. }
  281. }
  282. public bool UseBinary {
  283. get {
  284. return binary;
  285. } set {
  286. CheckRequestStarted ();
  287. binary = value;
  288. }
  289. }
  290. public override int Timeout {
  291. get {
  292. return timeout;
  293. }
  294. set {
  295. CheckRequestStarted ();
  296. if (value < -1)
  297. throw new ArgumentOutOfRangeException ();
  298. else
  299. timeout = value;
  300. }
  301. }
  302. string DataType {
  303. get {
  304. return binary ? "I" : "A";
  305. }
  306. }
  307. RequestState State {
  308. get {
  309. lock (locker) {
  310. return requestState;
  311. }
  312. }
  313. set {
  314. lock (locker) {
  315. CheckIfAborted ();
  316. CheckFinalState ();
  317. requestState = value;
  318. }
  319. }
  320. }
  321. public override void Abort () {
  322. lock (locker) {
  323. if (State == RequestState.TransferInProgress) {
  324. /*FtpStatus status = */
  325. SendCommand (false, AbortCommand);
  326. }
  327. if (!InFinalState ()) {
  328. State = RequestState.Aborted;
  329. ftpResponse = new FtpWebResponse (this, requestUri, method, FtpStatusCode.FileActionAborted, "Aborted by request");
  330. }
  331. }
  332. }
  333. public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state) {
  334. if (asyncResult != null && !asyncResult.IsCompleted) {
  335. throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
  336. }
  337. CheckIfAborted ();
  338. asyncResult = new FtpAsyncResult (callback, state);
  339. lock (locker) {
  340. if (InFinalState ())
  341. asyncResult.SetCompleted (true, ftpResponse);
  342. else {
  343. if (State == RequestState.Before)
  344. State = RequestState.Scheduled;
  345. Thread thread = new Thread (ProcessRequest);
  346. thread.Start ();
  347. }
  348. }
  349. return asyncResult;
  350. }
  351. public override WebResponse EndGetResponse (IAsyncResult asyncResult) {
  352. if (asyncResult == null)
  353. throw new ArgumentNullException ("AsyncResult cannot be null!");
  354. if (!(asyncResult is FtpAsyncResult) || asyncResult != this.asyncResult)
  355. throw new ArgumentException ("AsyncResult is from another request!");
  356. FtpAsyncResult asyncFtpResult = (FtpAsyncResult) asyncResult;
  357. if (!asyncFtpResult.WaitUntilComplete (timeout, false)) {
  358. Abort ();
  359. throw new WebException ("Transfer timed out.", WebExceptionStatus.Timeout);
  360. }
  361. CheckIfAborted ();
  362. asyncResult = null;
  363. if (asyncFtpResult.GotException)
  364. throw asyncFtpResult.Exception;
  365. return asyncFtpResult.Response;
  366. }
  367. public override WebResponse GetResponse () {
  368. IAsyncResult asyncResult = BeginGetResponse (null, null);
  369. return EndGetResponse (asyncResult);
  370. }
  371. public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state) {
  372. if (method != WebRequestMethods.Ftp.UploadFile && method != WebRequestMethods.Ftp.UploadFileWithUniqueName &&
  373. method != WebRequestMethods.Ftp.AppendFile)
  374. throw new ProtocolViolationException ();
  375. lock (locker) {
  376. CheckIfAborted ();
  377. if (State != RequestState.Before)
  378. throw new InvalidOperationException ("Cannot re-call BeginGetRequestStream/BeginGetResponse while a previous call is still in progress");
  379. State = RequestState.Scheduled;
  380. }
  381. asyncResult = new FtpAsyncResult (callback, state);
  382. Thread thread = new Thread (ProcessRequest);
  383. thread.Start ();
  384. return asyncResult;
  385. }
  386. public override Stream EndGetRequestStream (IAsyncResult asyncResult) {
  387. if (asyncResult == null)
  388. throw new ArgumentNullException ("asyncResult");
  389. if (!(asyncResult is FtpAsyncResult))
  390. throw new ArgumentException ("asyncResult");
  391. if (State == RequestState.Aborted) {
  392. throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
  393. }
  394. if (asyncResult != this.asyncResult)
  395. throw new ArgumentException ("AsyncResult is from another request!");
  396. FtpAsyncResult res = (FtpAsyncResult) asyncResult;
  397. if (!res.WaitUntilComplete (timeout, false)) {
  398. Abort ();
  399. throw new WebException ("Request timed out");
  400. }
  401. if (res.GotException)
  402. throw res.Exception;
  403. return res.Stream;
  404. }
  405. public override Stream GetRequestStream () {
  406. IAsyncResult asyncResult = BeginGetRequestStream (null, null);
  407. return EndGetRequestStream (asyncResult);
  408. }
  409. ServicePoint GetServicePoint ()
  410. {
  411. if (servicePoint == null)
  412. servicePoint = ServicePointManager.FindServicePoint (requestUri, proxy);
  413. return servicePoint;
  414. }
  415. // Probably move some code of command connection here
  416. void ResolveHost ()
  417. {
  418. CheckIfAborted ();
  419. hostEntry = GetServicePoint ().HostEntry;
  420. if (hostEntry == null) {
  421. ftpResponse.UpdateStatus (new FtpStatus(FtpStatusCode.ActionAbortedLocalProcessingError, "Cannot resolve server name"));
  422. throw new WebException ("The remote server name could not be resolved: " + requestUri,
  423. null, WebExceptionStatus.NameResolutionFailure, ftpResponse);
  424. }
  425. }
  426. void ProcessRequest () {
  427. if (State == RequestState.Scheduled) {
  428. ftpResponse = new FtpWebResponse (this, requestUri, method, keepAlive);
  429. try {
  430. ProcessMethod ();
  431. //State = RequestState.Finished;
  432. //finalResponse = ftpResponse;
  433. asyncResult.SetCompleted (false, ftpResponse);
  434. }
  435. catch (Exception e) {
  436. if (!GetServicePoint ().UsesProxy)
  437. State = RequestState.Error;
  438. SetCompleteWithError (e);
  439. }
  440. }
  441. else {
  442. if (InProgress ()) {
  443. FtpStatus status = GetResponseStatus ();
  444. ftpResponse.UpdateStatus (status);
  445. if (ftpResponse.IsFinal ()) {
  446. State = RequestState.Finished;
  447. }
  448. }
  449. asyncResult.SetCompleted (false, ftpResponse);
  450. }
  451. }
  452. void SetType ()
  453. {
  454. if (binary) {
  455. FtpStatus status = SendCommand (TypeCommand, DataType);
  456. if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
  457. throw CreateExceptionFromResponse (status);
  458. }
  459. }
  460. string GetRemoteFolderPath (Uri uri)
  461. {
  462. string result;
  463. string local_path = Uri.UnescapeDataString (uri.LocalPath);
  464. if (initial_path == null || initial_path == "/") {
  465. result = local_path;
  466. } else {
  467. if (local_path [0] == '/')
  468. local_path = local_path.Substring (1);
  469. Uri initial = new Uri ("ftp://dummy-host" + initial_path);
  470. result = new Uri (initial, local_path).LocalPath;
  471. }
  472. int last = result.LastIndexOf ('/');
  473. if (last == -1)
  474. return null;
  475. return result.Substring (0, last + 1);
  476. }
  477. void CWDAndSetFileName (Uri uri)
  478. {
  479. string remote_folder = GetRemoteFolderPath (uri);
  480. FtpStatus status;
  481. if (remote_folder != null) {
  482. status = SendCommand (ChangeDir, remote_folder);
  483. if ((int) status.StatusCode < 200 || (int) status.StatusCode >= 300)
  484. throw CreateExceptionFromResponse (status);
  485. int last = uri.LocalPath.LastIndexOf ('/');
  486. if (last >= 0) {
  487. file_name = Uri.UnescapeDataString (uri.LocalPath.Substring (last + 1));
  488. }
  489. }
  490. }
  491. void ProcessMethod ()
  492. {
  493. ServicePoint sp = GetServicePoint ();
  494. if (sp.UsesProxy) {
  495. if (method != WebRequestMethods.Ftp.DownloadFile)
  496. throw new NotSupportedException ("FTP+proxy only supports RETR");
  497. HttpWebRequest req = (HttpWebRequest) WebRequest.Create (proxy.GetProxy (requestUri));
  498. req.Address = requestUri;
  499. requestState = RequestState.Finished;
  500. WebResponse response = req.GetResponse ();
  501. ftpResponse.Stream = new FtpDataStream (this, response.GetResponseStream (), true);
  502. ftpResponse.StatusCode = FtpStatusCode.CommandOK;
  503. return;
  504. }
  505. State = RequestState.Connecting;
  506. ResolveHost ();
  507. OpenControlConnection ();
  508. CWDAndSetFileName (requestUri);
  509. SetType ();
  510. switch (method) {
  511. // Open data connection and receive data
  512. case WebRequestMethods.Ftp.DownloadFile:
  513. case WebRequestMethods.Ftp.ListDirectory:
  514. case WebRequestMethods.Ftp.ListDirectoryDetails:
  515. DownloadData ();
  516. break;
  517. // Open data connection and send data
  518. case WebRequestMethods.Ftp.AppendFile:
  519. case WebRequestMethods.Ftp.UploadFile:
  520. case WebRequestMethods.Ftp.UploadFileWithUniqueName:
  521. UploadData ();
  522. break;
  523. // Get info from control connection
  524. case WebRequestMethods.Ftp.GetFileSize:
  525. case WebRequestMethods.Ftp.GetDateTimestamp:
  526. case WebRequestMethods.Ftp.PrintWorkingDirectory:
  527. case WebRequestMethods.Ftp.MakeDirectory:
  528. case WebRequestMethods.Ftp.Rename:
  529. case WebRequestMethods.Ftp.DeleteFile:
  530. ProcessSimpleMethod ();
  531. break;
  532. default: // What to do here?
  533. throw new Exception (String.Format ("Support for command {0} not implemented yet", method));
  534. }
  535. CheckIfAborted ();
  536. }
  537. private void CloseControlConnection () {
  538. if (controlStream != null) {
  539. SendCommand (QuitCommand);
  540. controlStream.Close ();
  541. controlStream = null;
  542. }
  543. }
  544. internal void CloseDataConnection () {
  545. if(origDataStream != null) {
  546. origDataStream.Close ();
  547. origDataStream = null;
  548. }
  549. }
  550. private void CloseConnection () {
  551. CloseControlConnection ();
  552. CloseDataConnection ();
  553. }
  554. void ProcessSimpleMethod ()
  555. {
  556. State = RequestState.TransferInProgress;
  557. FtpStatus status;
  558. if (method == WebRequestMethods.Ftp.PrintWorkingDirectory)
  559. method = "PWD";
  560. if (method == WebRequestMethods.Ftp.Rename)
  561. method = RenameFromCommand;
  562. status = SendCommand (method, file_name);
  563. ftpResponse.Stream = Stream.Null;
  564. string desc = status.StatusDescription;
  565. switch (method) {
  566. case WebRequestMethods.Ftp.GetFileSize: {
  567. if (status.StatusCode != FtpStatusCode.FileStatus)
  568. throw CreateExceptionFromResponse (status);
  569. int i, len;
  570. long size;
  571. for (i = 4, len = 0; i < desc.Length && Char.IsDigit (desc [i]); i++, len++)
  572. ;
  573. if (len == 0)
  574. throw new WebException ("Bad format for server response in " + method);
  575. if (!Int64.TryParse (desc.Substring (4, len), out size))
  576. throw new WebException ("Bad format for server response in " + method);
  577. ftpResponse.contentLength = size;
  578. }
  579. break;
  580. case WebRequestMethods.Ftp.GetDateTimestamp:
  581. if (status.StatusCode != FtpStatusCode.FileStatus)
  582. throw CreateExceptionFromResponse (status);
  583. ftpResponse.LastModified = DateTime.ParseExact (desc.Substring (4), "yyyyMMddHHmmss", null);
  584. break;
  585. case WebRequestMethods.Ftp.MakeDirectory:
  586. if (status.StatusCode != FtpStatusCode.PathnameCreated)
  587. throw CreateExceptionFromResponse (status);
  588. break;
  589. case ChangeDir:
  590. method = WebRequestMethods.Ftp.PrintWorkingDirectory;
  591. if (status.StatusCode != FtpStatusCode.FileActionOK)
  592. throw CreateExceptionFromResponse (status);
  593. status = SendCommand (method);
  594. if (status.StatusCode != FtpStatusCode.PathnameCreated)
  595. throw CreateExceptionFromResponse (status);
  596. break;
  597. case RenameFromCommand:
  598. method = WebRequestMethods.Ftp.Rename;
  599. if (status.StatusCode != FtpStatusCode.FileCommandPending)
  600. throw CreateExceptionFromResponse (status);
  601. // Pass an empty string if RenameTo wasn't specified
  602. status = SendCommand (RenameToCommand, renameTo != null ? renameTo : String.Empty);
  603. if (status.StatusCode != FtpStatusCode.FileActionOK)
  604. throw CreateExceptionFromResponse (status);
  605. break;
  606. case WebRequestMethods.Ftp.DeleteFile:
  607. if (status.StatusCode != FtpStatusCode.FileActionOK) {
  608. throw CreateExceptionFromResponse (status);
  609. }
  610. break;
  611. }
  612. State = RequestState.Finished;
  613. }
  614. void UploadData ()
  615. {
  616. State = RequestState.OpeningData;
  617. OpenDataConnection ();
  618. State = RequestState.TransferInProgress;
  619. requestStream = new FtpDataStream (this, dataStream, false);
  620. asyncResult.Stream = requestStream;
  621. }
  622. void DownloadData ()
  623. {
  624. State = RequestState.OpeningData;
  625. OpenDataConnection ();
  626. State = RequestState.TransferInProgress;
  627. ftpResponse.Stream = new FtpDataStream (this, dataStream, true);
  628. }
  629. void CheckRequestStarted ()
  630. {
  631. if (State != RequestState.Before)
  632. throw new InvalidOperationException ("There is a request currently in progress");
  633. }
  634. void OpenControlConnection ()
  635. {
  636. Exception exception = null;
  637. Socket sock = null;
  638. foreach (IPAddress address in hostEntry.AddressList) {
  639. sock = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  640. IPEndPoint remote = new IPEndPoint (address, requestUri.Port);
  641. if (!ServicePoint.CallEndPointDelegate (sock, remote)) {
  642. sock.Close ();
  643. sock = null;
  644. } else {
  645. try {
  646. sock.Connect (remote);
  647. localEndPoint = (IPEndPoint) sock.LocalEndPoint;
  648. break;
  649. } catch (SocketException exc) {
  650. exception = exc;
  651. sock.Close ();
  652. sock = null;
  653. }
  654. }
  655. }
  656. // Couldn't connect to any address
  657. if (sock == null)
  658. throw new WebException ("Unable to connect to remote server", exception,
  659. WebExceptionStatus.UnknownError, ftpResponse);
  660. controlStream = new NetworkStream (sock);
  661. controlReader = new StreamReader (controlStream, Encoding.ASCII);
  662. State = RequestState.Authenticating;
  663. Authenticate ();
  664. FtpStatus status = SendCommand ("OPTS", "utf8", "on");
  665. // ignore status for OPTS
  666. status = SendCommand (WebRequestMethods.Ftp.PrintWorkingDirectory);
  667. initial_path = GetInitialPath (status);
  668. }
  669. static string GetInitialPath (FtpStatus status)
  670. {
  671. int s = (int) status.StatusCode;
  672. if (s < 200 || s > 300 || status.StatusDescription.Length <= 4)
  673. throw new WebException ("Error getting current directory: " + status.StatusDescription, null,
  674. WebExceptionStatus.UnknownError, null);
  675. string msg = status.StatusDescription.Substring (4);
  676. if (msg [0] == '"') {
  677. int next_quote = msg.IndexOf ('\"', 1);
  678. if (next_quote == -1)
  679. throw new WebException ("Error getting current directory: PWD -> " + status.StatusDescription, null,
  680. WebExceptionStatus.UnknownError, null);
  681. msg = msg.Substring (1, next_quote - 1);
  682. }
  683. if (!msg.EndsWith ("/"))
  684. msg += "/";
  685. return msg;
  686. }
  687. // Probably we could do better having here a regex
  688. Socket SetupPassiveConnection (string statusDescription)
  689. {
  690. // Current response string
  691. string response = statusDescription;
  692. if (response.Length < 4)
  693. throw new WebException ("Cannot open passive data connection");
  694. // Look for first digit after code
  695. int i;
  696. for (i = 3; i < response.Length && !Char.IsDigit (response [i]); i++)
  697. ;
  698. if (i >= response.Length)
  699. throw new WebException ("Cannot open passive data connection");
  700. // Get six elements
  701. string [] digits = response.Substring (i).Split (new char [] {','}, 6);
  702. if (digits.Length != 6)
  703. throw new WebException ("Cannot open passive data connection");
  704. // Clean non-digits at the end of last element
  705. int j;
  706. for (j = digits [5].Length - 1; j >= 0 && !Char.IsDigit (digits [5][j]); j--)
  707. ;
  708. if (j < 0)
  709. throw new WebException ("Cannot open passive data connection");
  710. digits [5] = digits [5].Substring (0, j + 1);
  711. IPAddress ip;
  712. try {
  713. ip = IPAddress.Parse (String.Join (".", digits, 0, 4));
  714. } catch (FormatException) {
  715. throw new WebException ("Cannot open passive data connection");
  716. }
  717. // Get the port
  718. int p1, p2, port;
  719. if (!Int32.TryParse (digits [4], out p1) || !Int32.TryParse (digits [5], out p2))
  720. throw new WebException ("Cannot open passive data connection");
  721. port = (p1 << 8) + p2; // p1 * 256 + p2
  722. //port = p1 * 256 + p2;
  723. if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
  724. throw new WebException ("Cannot open passive data connection");
  725. IPEndPoint ep = new IPEndPoint (ip, port);
  726. Socket sock = new Socket (ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  727. try {
  728. sock.Connect (ep);
  729. } catch (SocketException) {
  730. sock.Close ();
  731. throw new WebException ("Cannot open passive data connection");
  732. }
  733. return sock;
  734. }
  735. Exception CreateExceptionFromResponse (FtpStatus status)
  736. {
  737. FtpWebResponse ftpResponse = new FtpWebResponse (this, requestUri, method, status);
  738. WebException exc = new WebException ("Server returned an error: " + status.StatusDescription,
  739. null, WebExceptionStatus.ProtocolError, ftpResponse);
  740. return exc;
  741. }
  742. // Here we could also get a server error, so be cautious
  743. internal void SetTransferCompleted ()
  744. {
  745. if (InFinalState ())
  746. return;
  747. State = RequestState.Finished;
  748. FtpStatus status = GetResponseStatus ();
  749. ftpResponse.UpdateStatus (status);
  750. if(!keepAlive)
  751. CloseConnection ();
  752. }
  753. internal void OperationCompleted ()
  754. {
  755. if(!keepAlive)
  756. CloseConnection ();
  757. }
  758. void SetCompleteWithError (Exception exc)
  759. {
  760. if (asyncResult != null) {
  761. asyncResult.SetCompleted (false, exc);
  762. }
  763. }
  764. Socket InitDataConnection ()
  765. {
  766. FtpStatus status;
  767. if (usePassive) {
  768. status = SendCommand (PassiveCommand);
  769. if (status.StatusCode != FtpStatusCode.EnteringPassive) {
  770. throw CreateExceptionFromResponse (status);
  771. }
  772. return SetupPassiveConnection (status.StatusDescription);
  773. }
  774. // Open a socket to listen the server's connection
  775. Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  776. try {
  777. sock.Bind (new IPEndPoint (localEndPoint.Address, 0));
  778. sock.Listen (1); // We only expect a connection from server
  779. } catch (SocketException e) {
  780. sock.Close ();
  781. throw new WebException ("Couldn't open listening socket on client", e);
  782. }
  783. IPEndPoint ep = (IPEndPoint) sock.LocalEndPoint;
  784. string ipString = ep.Address.ToString ().Replace ('.', ',');
  785. int h1 = ep.Port >> 8; // ep.Port / 256
  786. int h2 = ep.Port % 256;
  787. string portParam = ipString + "," + h1 + "," + h2;
  788. status = SendCommand (PortCommand, portParam);
  789. if (status.StatusCode != FtpStatusCode.CommandOK) {
  790. sock.Close ();
  791. throw (CreateExceptionFromResponse (status));
  792. }
  793. return sock;
  794. }
  795. void OpenDataConnection ()
  796. {
  797. FtpStatus status;
  798. Socket s = InitDataConnection ();
  799. // Handle content offset
  800. if (offset > 0) {
  801. status = SendCommand (RestCommand, offset.ToString ());
  802. if (status.StatusCode != FtpStatusCode.FileCommandPending)
  803. throw CreateExceptionFromResponse (status);
  804. }
  805. if (method != WebRequestMethods.Ftp.ListDirectory && method != WebRequestMethods.Ftp.ListDirectoryDetails &&
  806. method != WebRequestMethods.Ftp.UploadFileWithUniqueName) {
  807. status = SendCommand (method, file_name);
  808. } else {
  809. status = SendCommand (method);
  810. }
  811. if (status.StatusCode != FtpStatusCode.OpeningData && status.StatusCode != FtpStatusCode.DataAlreadyOpen)
  812. throw CreateExceptionFromResponse (status);
  813. if (usePassive) {
  814. origDataStream = new NetworkStream (s, true);
  815. dataStream = origDataStream;
  816. if (EnableSsl)
  817. ChangeToSSLSocket (ref dataStream);
  818. }
  819. else {
  820. // Active connection (use Socket.Blocking to true)
  821. Socket incoming = null;
  822. try {
  823. incoming = s.Accept ();
  824. }
  825. catch (SocketException) {
  826. s.Close ();
  827. if (incoming != null)
  828. incoming.Close ();
  829. throw new ProtocolViolationException ("Server commited a protocol violation.");
  830. }
  831. s.Close ();
  832. origDataStream = new NetworkStream (incoming, true);
  833. dataStream = origDataStream;
  834. if (EnableSsl)
  835. ChangeToSSLSocket (ref dataStream);
  836. }
  837. ftpResponse.UpdateStatus (status);
  838. }
  839. void Authenticate ()
  840. {
  841. string username = null;
  842. string password = null;
  843. string domain = null;
  844. if (credentials != null) {
  845. username = credentials.UserName;
  846. password = credentials.Password;
  847. domain = credentials.Domain;
  848. }
  849. if (username == null)
  850. username = "anonymous";
  851. if (password == null)
  852. password = "@anonymous";
  853. if (!string.IsNullOrEmpty (domain))
  854. username = domain + '\\' + username;
  855. // Connect to server and get banner message
  856. FtpStatus status = GetResponseStatus ();
  857. ftpResponse.BannerMessage = status.StatusDescription;
  858. if (EnableSsl) {
  859. InitiateSecureConnection (ref controlStream);
  860. controlReader = new StreamReader (controlStream, Encoding.ASCII);
  861. status = SendCommand ("PBSZ", "0");
  862. int st = (int) status.StatusCode;
  863. if (st < 200 || st >= 300)
  864. throw CreateExceptionFromResponse (status);
  865. // TODO: what if "PROT P" is denied by the server? What does MS do?
  866. status = SendCommand ("PROT", "P");
  867. st = (int) status.StatusCode;
  868. if (st < 200 || st >= 300)
  869. throw CreateExceptionFromResponse (status);
  870. status = new FtpStatus (FtpStatusCode.SendUserCommand, "");
  871. }
  872. if (status.StatusCode != FtpStatusCode.SendUserCommand)
  873. throw CreateExceptionFromResponse (status);
  874. status = SendCommand (UserCommand, username);
  875. switch (status.StatusCode) {
  876. case FtpStatusCode.SendPasswordCommand:
  877. status = SendCommand (PasswordCommand, password);
  878. if (status.StatusCode != FtpStatusCode.LoggedInProceed)
  879. throw CreateExceptionFromResponse (status);
  880. break;
  881. case FtpStatusCode.LoggedInProceed:
  882. break;
  883. default:
  884. throw CreateExceptionFromResponse (status);
  885. }
  886. ftpResponse.WelcomeMessage = status.StatusDescription;
  887. ftpResponse.UpdateStatus (status);
  888. }
  889. FtpStatus SendCommand (string command, params string [] parameters) {
  890. return SendCommand (true, command, parameters);
  891. }
  892. FtpStatus SendCommand (bool waitResponse, string command, params string [] parameters)
  893. {
  894. byte [] cmd;
  895. string commandString = command;
  896. if (parameters.Length > 0)
  897. commandString += " " + String.Join (" ", parameters);
  898. commandString += EOL;
  899. cmd = Encoding.ASCII.GetBytes (commandString);
  900. try {
  901. controlStream.Write (cmd, 0, cmd.Length);
  902. } catch (IOException) {
  903. //controlStream.Close ();
  904. return new FtpStatus(FtpStatusCode.ServiceNotAvailable, "Write failed");
  905. }
  906. if(!waitResponse)
  907. return null;
  908. FtpStatus result = GetResponseStatus ();
  909. if (ftpResponse != null)
  910. ftpResponse.UpdateStatus (result);
  911. return result;
  912. }
  913. internal static FtpStatus ServiceNotAvailable ()
  914. {
  915. return new FtpStatus (FtpStatusCode.ServiceNotAvailable, Locale.GetText ("Invalid response from server"));
  916. }
  917. internal FtpStatus GetResponseStatus ()
  918. {
  919. while (true) {
  920. string response = null;
  921. try {
  922. response = controlReader.ReadLine ();
  923. } catch (IOException) {
  924. }
  925. if (response == null || response.Length < 3)
  926. return ServiceNotAvailable ();
  927. int code;
  928. if (!Int32.TryParse (response.Substring (0, 3), out code))
  929. return ServiceNotAvailable ();
  930. if (response.Length > 3 && response [3] == '-'){
  931. string line = null;
  932. string find = code.ToString() + ' ';
  933. while (true){
  934. line = null;
  935. try {
  936. line = controlReader.ReadLine();
  937. } catch (IOException) {
  938. }
  939. if (line == null)
  940. return ServiceNotAvailable ();
  941. response += Environment.NewLine + line;
  942. if (line.StartsWith(find, StringComparison.Ordinal))
  943. break;
  944. }
  945. }
  946. return new FtpStatus ((FtpStatusCode) code, response);
  947. }
  948. }
  949. private void InitiateSecureConnection (ref Stream stream) {
  950. FtpStatus status = SendCommand (AuthCommand, "TLS");
  951. if (status.StatusCode != FtpStatusCode.ServerWantsSecureSession)
  952. throw CreateExceptionFromResponse (status);
  953. ChangeToSSLSocket (ref stream);
  954. }
  955. #if SECURITY_DEP
  956. RemoteCertificateValidationCallback callback = delegate (object sender,
  957. X509Certificate certificate,
  958. X509Chain chain,
  959. SslPolicyErrors sslPolicyErrors) {
  960. // honor any exciting callback defined on ServicePointManager
  961. if (ServicePointManager.ServerCertificateValidationCallback != null)
  962. return ServicePointManager.ServerCertificateValidationCallback (sender, certificate, chain, sslPolicyErrors);
  963. // otherwise provide our own
  964. if (sslPolicyErrors != SslPolicyErrors.None)
  965. throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors);
  966. return true;
  967. };
  968. #endif
  969. internal bool ChangeToSSLSocket (ref Stream stream) {
  970. #if TARGET_JVM
  971. stream.ChangeToSSLSocket ();
  972. return true;
  973. #elif SECURITY_DEP
  974. SslStream sslStream = new SslStream (stream, true, callback, null);
  975. //sslStream.AuthenticateAsClient (Host, this.ClientCertificates, SslProtocols.Default, false);
  976. //TODO: client certificates
  977. sslStream.AuthenticateAsClient (requestUri.Host, null, SslProtocols.Default, false);
  978. stream = sslStream;
  979. return true;
  980. #else
  981. throw new NotImplementedException ();
  982. #endif
  983. }
  984. bool InFinalState () {
  985. return (State == RequestState.Aborted || State == RequestState.Error || State == RequestState.Finished);
  986. }
  987. bool InProgress () {
  988. return (State != RequestState.Before && !InFinalState ());
  989. }
  990. internal void CheckIfAborted () {
  991. if (State == RequestState.Aborted)
  992. throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
  993. }
  994. void CheckFinalState () {
  995. if (InFinalState ())
  996. throw new InvalidOperationException ("Cannot change final state");
  997. }
  998. }
  999. }