SftpSession.cs 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading;
  6. using Renci.SshNet.Channels;
  7. using Renci.SshNet.Common;
  8. using System.Diagnostics;
  9. using System.Collections.Generic;
  10. using System.Globalization;
  11. using Renci.SshNet.Sftp.Responses;
  12. using Renci.SshNet.Sftp.Requests;
  13. namespace Renci.SshNet.Sftp
  14. {
  15. internal class SftpSession : IDisposable
  16. {
  17. private Session _session;
  18. private ChannelSession _channel;
  19. private Dictionary<uint, SftpRequest> _requests = new Dictionary<uint, SftpRequest>();
  20. private List<byte> _data = new List<byte>(32 * 1024);
  21. private Exception _exception;
  22. private EventWaitHandle _errorOccuredWaitHandle = new AutoResetEvent(false);
  23. private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(false);
  24. private TimeSpan _operationTimeout;
  25. public event EventHandler<ExceptionEventArgs> ErrorOccured;
  26. /// <summary>
  27. /// Gets remote working directory.
  28. /// </summary>
  29. public string WorkingDirectory { get; private set; }
  30. /// <summary>
  31. /// Gets SFTP protocol version.
  32. /// </summary>
  33. public int ProtocolVersion { get; private set; }
  34. private uint _requestId;
  35. /// <summary>
  36. /// Gets the next request id for sftp session.
  37. /// </summary>
  38. public uint NextRequestId
  39. {
  40. get
  41. {
  42. return this._requestId++;
  43. }
  44. }
  45. #region SFTP messages
  46. //internal event EventHandler<MessageEventArgs<StatusMessage>> StatusMessageReceived;
  47. //internal event EventHandler<MessageEventArgs<DataMessage>> DataMessageReceived;
  48. //internal event EventHandler<MessageEventArgs<HandleMessage>> HandleMessageReceived;
  49. //internal event EventHandler<MessageEventArgs<NameMessage>> NameMessageReceived;
  50. //internal event EventHandler<MessageEventArgs<AttributesMessage>> AttributesMessageReceived;
  51. #endregion
  52. public SftpSession(Session session, TimeSpan operationTimeout)
  53. {
  54. this._session = session;
  55. this._operationTimeout = operationTimeout;
  56. }
  57. public void Connect()
  58. {
  59. this._channel = this._session.CreateChannel<ChannelSession>();
  60. this._session.ErrorOccured += Session_ErrorOccured;
  61. this._session.Disconnected += Session_Disconnected;
  62. this._channel.DataReceived += Channel_DataReceived;
  63. this._channel.Open();
  64. this._channel.SendSubsystemRequest("sftp");
  65. this.SendMessage(new SftpInitRequest(3));
  66. this.WaitHandle(this._sftpVersionConfirmed, this._operationTimeout);
  67. this.ProtocolVersion = 3;
  68. // Resolve current directory
  69. this.WorkingDirectory = this.RequestRealPath(".").Keys.First();
  70. }
  71. public void Disconnect()
  72. {
  73. this.Dispose();
  74. }
  75. public void ChangeDirectory(string path)
  76. {
  77. var fullPath = this.GetCanonicalPath(path);
  78. var handle = this.RequestOpenDir(fullPath);
  79. this.RequestClose(handle);
  80. this.WorkingDirectory = fullPath;
  81. }
  82. private void Channel_DataReceived(object sender, Common.ChannelDataEventArgs e)
  83. {
  84. // Add channel data to internal data holder
  85. this._data.AddRange(e.Data);
  86. while (this._data.Count > 4 + 1)
  87. {
  88. // Extract packet length
  89. var packetLength = (this._data[0] << 24 | this._data[1] << 16 | this._data[2] << 8 | this._data[3]);
  90. // Check if complete packet data is available
  91. if (this._data.Count < packetLength + 4)
  92. {
  93. // Wait for complete message to arrive first
  94. break;
  95. }
  96. this._data.RemoveRange(0, 4);
  97. // Create buffer to hold packet data
  98. var packetData = new byte[packetLength];
  99. // Cope packet data to array
  100. this._data.CopyTo(0, packetData, 0, packetLength);
  101. // Remove loaded data from _data holder
  102. this._data.RemoveRange(0, packetLength);
  103. // Load SFTP Message and handle it
  104. dynamic response = SftpMessage.Load(packetData);
  105. try
  106. {
  107. if (response is SftpVersionResponse)
  108. {
  109. if (response.Version == 3)
  110. {
  111. this._sftpVersionConfirmed.Set();
  112. }
  113. else
  114. {
  115. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Server SFTP version {0} is not supported.", response.Version));
  116. }
  117. }
  118. else
  119. {
  120. this.HandleResponse(response);
  121. }
  122. }
  123. catch (Exception exp)
  124. {
  125. this.RaiseError(exp);
  126. break;
  127. }
  128. }
  129. }
  130. private void SendRequest(SftpRequest request)
  131. {
  132. lock (this._requests)
  133. {
  134. this._requests.Add(request.RequestId, request);
  135. }
  136. this._session.SendMessage(new SftpDataMessage(this._channel.RemoteChannelNumber, request));
  137. }
  138. internal void SendMessage(SftpMessage sftpMessage)
  139. {
  140. this._session.SendMessage(new SftpDataMessage(this._channel.RemoteChannelNumber, sftpMessage));
  141. }
  142. internal void WaitHandle(WaitHandle waitHandle, TimeSpan operationTimeout)
  143. {
  144. var waitHandles = new WaitHandle[]
  145. {
  146. this._errorOccuredWaitHandle,
  147. waitHandle,
  148. };
  149. var index = EventWaitHandle.WaitAny(waitHandles, operationTimeout);
  150. if (index < 1)
  151. {
  152. throw this._exception;
  153. }
  154. else if (index > 1)
  155. {
  156. // throw time out error
  157. throw new SshOperationTimeoutException(string.Format(CultureInfo.CurrentCulture, "Sftp operation has timed out."));
  158. }
  159. }
  160. /// <summary>
  161. /// Resolves path into absolute path on the server.
  162. /// </summary>
  163. /// <param name="path">Path to resolve.</param>
  164. /// <returns>Absolute path</returns>
  165. internal string GetCanonicalPath(string path)
  166. {
  167. var fullPath = path;
  168. if (!string.IsNullOrEmpty(path) && path[0] != '/' && this.WorkingDirectory != null)
  169. {
  170. if (this.WorkingDirectory[this.WorkingDirectory.Length - 1] == '/')
  171. {
  172. fullPath = string.Format(CultureInfo.InvariantCulture, "{0}{1}", this.WorkingDirectory, path);
  173. }
  174. else
  175. {
  176. fullPath = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", this.WorkingDirectory, path);
  177. }
  178. }
  179. var canonizedPath = string.Empty;
  180. var realPathFiles = this.RequestRealPath(fullPath, true);
  181. if ( realPathFiles != null)
  182. {
  183. canonizedPath = realPathFiles.Keys.First();
  184. }
  185. if (!string.IsNullOrEmpty(canonizedPath))
  186. return canonizedPath;
  187. // Check for special cases
  188. if (fullPath.EndsWith("/.", StringComparison.InvariantCultureIgnoreCase) ||
  189. fullPath.EndsWith("/..", StringComparison.InvariantCultureIgnoreCase) ||
  190. fullPath.Equals("/", StringComparison.InvariantCultureIgnoreCase) ||
  191. fullPath.IndexOf('/') < 0)
  192. return fullPath;
  193. var pathParts = fullPath.Split(new char[] { '/' });
  194. var partialFullPath = string.Join("/", pathParts, 0, pathParts.Length - 1);
  195. if (string.IsNullOrEmpty(partialFullPath))
  196. partialFullPath = "/";
  197. //canonizedPath = this.RequestRealPath(partialFullPath).First().FullName;
  198. realPathFiles = this.RequestRealPath(partialFullPath, true);
  199. if (realPathFiles != null)
  200. {
  201. canonizedPath = realPathFiles.Keys.First();
  202. }
  203. if (string.IsNullOrEmpty(canonizedPath))
  204. {
  205. return fullPath;
  206. }
  207. else
  208. {
  209. var slash = string.Empty;
  210. if (canonizedPath[canonizedPath.Length - 1] != '/')
  211. slash = "/";
  212. return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", canonizedPath, slash, pathParts[pathParts.Length - 1]);
  213. }
  214. }
  215. internal bool FileExistsCommand(string path, Flags flags)
  216. {
  217. var handle = this.RequestOpen(path, flags, true);
  218. if (handle == null)
  219. {
  220. return false;
  221. }
  222. else
  223. {
  224. this.RequestClose(handle);
  225. return true;
  226. }
  227. }
  228. #region SFTP API functions
  229. //#define SSH_FXP_INIT 1
  230. //#define SSH_FXP_VERSION 2
  231. /// <summary>
  232. /// Performs SSH_FXP_OPEN request
  233. /// </summary>
  234. /// <param name="path">The path.</param>
  235. /// <param name="flags">The flags.</param>
  236. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  237. /// <returns></returns>
  238. internal byte[] RequestOpen(string path, Flags flags, bool nullOnError = false)
  239. {
  240. byte[] handle = null;
  241. using (var wait = new AutoResetEvent(false))
  242. {
  243. var request = new SftpOpenRequest(this.NextRequestId, path, flags,
  244. (response) =>
  245. {
  246. handle = response.Handle;
  247. wait.Set();
  248. },
  249. (response) =>
  250. {
  251. if (nullOnError)
  252. {
  253. wait.Set();
  254. }
  255. else
  256. {
  257. this.ThrowSftpException(response);
  258. }
  259. });
  260. this.SendRequest(request);
  261. this.WaitHandle(wait, this._operationTimeout);
  262. }
  263. return handle;
  264. }
  265. /// <summary>
  266. /// Performs SSH_FXP_CLOSE request.
  267. /// </summary>
  268. /// <param name="handle">The handle.</param>
  269. internal void RequestClose(byte[] handle)
  270. {
  271. using (var wait = new AutoResetEvent(false))
  272. {
  273. var request = new SftpCloseRequest(this.NextRequestId, handle,
  274. (response) =>
  275. {
  276. if (response.StatusCode == StatusCodes.Ok)
  277. {
  278. wait.Set();
  279. }
  280. else
  281. {
  282. this.ThrowSftpException(response);
  283. }
  284. });
  285. this.SendRequest(request);
  286. this.WaitHandle(wait, this._operationTimeout);
  287. }
  288. }
  289. /// <summary>
  290. /// Performs SSH_FXP_READ request.
  291. /// </summary>
  292. /// <param name="handle">The handle.</param>
  293. /// <param name="offset">The offset.</param>
  294. /// <param name="length">The length.</param>
  295. /// <returns>data array; null if EOF</returns>
  296. internal byte[] RequestRead(byte[] handle, UInt64 offset, UInt32 length)
  297. {
  298. byte[] data = null;
  299. using (var wait = new AutoResetEvent(false))
  300. {
  301. var request = new SftpReadRequest(this.NextRequestId, handle, offset, length,
  302. (response) =>
  303. {
  304. data = response.Data;
  305. wait.Set();
  306. },
  307. (response) =>
  308. {
  309. if (response.StatusCode == StatusCodes.Eof)
  310. {
  311. wait.Set();
  312. }
  313. else
  314. {
  315. this.ThrowSftpException(response);
  316. }
  317. });
  318. this.SendRequest(request);
  319. this.WaitHandle(wait, this._operationTimeout);
  320. }
  321. return data;
  322. }
  323. /// <summary>
  324. /// Performs SSH_FXP_WRITE request.
  325. /// </summary>
  326. /// <param name="handle">The handle.</param>
  327. /// <param name="offset">The offset.</param>
  328. /// <param name="data">The data.</param>
  329. internal void RequestWrite(byte[] handle, UInt64 offset, byte[] data)
  330. {
  331. using (var wait = new AutoResetEvent(false))
  332. {
  333. var request = new SftpWriteRequest(this.NextRequestId, handle, offset, data,
  334. (response) =>
  335. {
  336. if (response.StatusCode == StatusCodes.Ok)
  337. {
  338. wait.Set();
  339. }
  340. else
  341. {
  342. this.ThrowSftpException(response);
  343. }
  344. });
  345. this.SendRequest(request);
  346. this.WaitHandle(wait, this._operationTimeout);
  347. }
  348. }
  349. /// <summary>
  350. /// Performs SSH_FXP_LSTAT request.
  351. /// </summary>
  352. /// <param name="path">The path.</param>
  353. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  354. /// <returns>
  355. /// File attributes
  356. /// </returns>
  357. internal SftpFileAttributes RequestLStat(string path, bool nullOnError = false)
  358. {
  359. SftpFileAttributes attributes = null;
  360. using (var wait = new AutoResetEvent(false))
  361. {
  362. var request = new SftpLStatRequest(this.NextRequestId, path,
  363. (response) =>
  364. {
  365. attributes = response.Attributes;
  366. wait.Set();
  367. },
  368. (response) =>
  369. {
  370. this.ThrowSftpException(response);
  371. });
  372. this.SendRequest(request);
  373. this.WaitHandle(wait, this._operationTimeout);
  374. }
  375. return attributes;
  376. }
  377. /// <summary>
  378. /// Performs SSH_FXP_FSTAT request.
  379. /// </summary>
  380. /// <param name="handle">The handle.</param>
  381. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  382. /// <returns>
  383. /// File attributes
  384. /// </returns>
  385. internal SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError = false)
  386. {
  387. SftpFileAttributes attributes = null;
  388. using (var wait = new AutoResetEvent(false))
  389. {
  390. var request = new SftpFStatRequest(this.NextRequestId, handle,
  391. (response) =>
  392. {
  393. attributes = response.Attributes;
  394. wait.Set();
  395. },
  396. (response) =>
  397. {
  398. this.ThrowSftpException(response);
  399. });
  400. this.SendRequest(request);
  401. this.WaitHandle(wait, this._operationTimeout);
  402. }
  403. return attributes;
  404. }
  405. /// <summary>
  406. /// Performs SSH_FXP_SETSTAT request.
  407. /// </summary>
  408. /// <param name="path">The path.</param>
  409. /// <param name="attributes">The attributes.</param>
  410. internal void RequestSetStat(string path, SftpFileAttributes attributes)
  411. {
  412. using (var wait = new AutoResetEvent(false))
  413. {
  414. var request = new SftpSetStatRequest(this.NextRequestId, path, attributes,
  415. (response) =>
  416. {
  417. if (response.StatusCode == StatusCodes.Ok)
  418. {
  419. wait.Set();
  420. }
  421. else
  422. {
  423. this.ThrowSftpException(response);
  424. }
  425. });
  426. this.SendRequest(request);
  427. this.WaitHandle(wait, this._operationTimeout);
  428. }
  429. }
  430. /// <summary>
  431. /// Performs SSH_FXP_FSETSTAT request.
  432. /// </summary>
  433. /// <param name="handle">The handle.</param>
  434. /// <param name="attributes">The attributes.</param>
  435. internal void RequestFSetStat(byte[] handle, SftpFileAttributes attributes)
  436. {
  437. using (var wait = new AutoResetEvent(false))
  438. {
  439. var request = new SftpFSetStatRequest(this.NextRequestId, handle, attributes,
  440. (response) =>
  441. {
  442. if (response.StatusCode == StatusCodes.Ok)
  443. {
  444. wait.Set();
  445. }
  446. else
  447. {
  448. this.ThrowSftpException(response);
  449. }
  450. });
  451. this.SendRequest(request);
  452. this.WaitHandle(wait, this._operationTimeout);
  453. }
  454. }
  455. /// <summary>
  456. /// Performs SSH_FXP_OPENDIR request
  457. /// </summary>
  458. /// <param name="path">The path.</param>
  459. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  460. /// <returns></returns>
  461. internal byte[] RequestOpenDir(string path, bool nullOnError = false)
  462. {
  463. byte[] handle = null;
  464. using (var wait = new AutoResetEvent(false))
  465. {
  466. var request = new SftpOpenDirRequest(this.NextRequestId, path,
  467. (response) =>
  468. {
  469. handle = response.Handle;
  470. wait.Set();
  471. },
  472. (response) =>
  473. {
  474. if (nullOnError)
  475. {
  476. wait.Set();
  477. }
  478. else
  479. {
  480. this.ThrowSftpException(response);
  481. }
  482. });
  483. this.SendRequest(request);
  484. this.WaitHandle(wait, this._operationTimeout);
  485. }
  486. return handle;
  487. }
  488. /// <summary>
  489. /// Performs SSH_FXP_READDIR request
  490. /// </summary>
  491. /// <param name="handle">The handle.</param>
  492. /// <returns></returns>
  493. internal IDictionary<string, SftpFileAttributes> RequestReadDir(byte[] handle)
  494. {
  495. IDictionary<string, SftpFileAttributes> result = null;
  496. using (var wait = new AutoResetEvent(false))
  497. {
  498. var request = new SftpReadDirRequest(this.NextRequestId, handle,
  499. (response) =>
  500. {
  501. result = response.Files;
  502. wait.Set();
  503. },
  504. (response) =>
  505. {
  506. if (response.StatusCode == StatusCodes.Eof)
  507. {
  508. wait.Set();
  509. }
  510. else
  511. {
  512. this.ThrowSftpException(response);
  513. }
  514. });
  515. this.SendRequest(request);
  516. this.WaitHandle(wait, this._operationTimeout);
  517. }
  518. return result;
  519. }
  520. /// <summary>
  521. /// Performs SSH_FXP_REMOVE request.
  522. /// </summary>
  523. /// <param name="path">The path.</param>
  524. internal void RequestRemove(string path)
  525. {
  526. using (var wait = new AutoResetEvent(false))
  527. {
  528. var request = new SftpRemoveRequest(this.NextRequestId, path,
  529. (response) =>
  530. {
  531. if (response.StatusCode == StatusCodes.Ok)
  532. {
  533. wait.Set();
  534. }
  535. else
  536. {
  537. this.ThrowSftpException(response);
  538. }
  539. });
  540. this.SendRequest(request);
  541. this.WaitHandle(wait, this._operationTimeout);
  542. }
  543. }
  544. /// <summary>
  545. /// Performs SSH_FXP_MKDIR request.
  546. /// </summary>
  547. /// <param name="path">The path.</param>
  548. internal void RequestMkDir(string path)
  549. {
  550. using (var wait = new AutoResetEvent(false))
  551. {
  552. var request = new SftpMkDirRequest(this.NextRequestId, path,
  553. (response) =>
  554. {
  555. if (response.StatusCode == StatusCodes.Ok)
  556. {
  557. wait.Set();
  558. }
  559. else
  560. {
  561. this.ThrowSftpException(response);
  562. }
  563. });
  564. this.SendRequest(request);
  565. this.WaitHandle(wait, this._operationTimeout);
  566. }
  567. }
  568. /// <summary>
  569. /// Performs SSH_FXP_RMDIR request.
  570. /// </summary>
  571. /// <param name="path">The path.</param>
  572. internal void RequestRmDir(string path)
  573. {
  574. using (var wait = new AutoResetEvent(false))
  575. {
  576. var request = new SftpRmDirRequest(this.NextRequestId, path,
  577. (response) =>
  578. {
  579. if (response.StatusCode == StatusCodes.Ok)
  580. {
  581. wait.Set();
  582. }
  583. else
  584. {
  585. this.ThrowSftpException(response);
  586. }
  587. });
  588. this.SendRequest(request);
  589. this.WaitHandle(wait, this._operationTimeout);
  590. }
  591. }
  592. /// <summary>
  593. /// Performs SSH_FXP_REALPATH request
  594. /// </summary>
  595. /// <param name="path">The path.</param>
  596. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  597. /// <returns></returns>
  598. internal IDictionary<string, SftpFileAttributes> RequestRealPath(string path, bool nullOnError = false)
  599. {
  600. IDictionary<string, SftpFileAttributes> result = null;
  601. using (var wait = new AutoResetEvent(false))
  602. {
  603. var request = new SftpRealPathRequest(this.NextRequestId, path,
  604. (response) =>
  605. {
  606. result = response.Files;
  607. wait.Set();
  608. },
  609. (response) =>
  610. {
  611. if (nullOnError)
  612. {
  613. wait.Set();
  614. }
  615. else
  616. {
  617. this.ThrowSftpException(response);
  618. }
  619. });
  620. this.SendRequest(request);
  621. this.WaitHandle(wait, this._operationTimeout);
  622. }
  623. return result;
  624. }
  625. /// <summary>
  626. /// Performs SSH_FXP_STAT request.
  627. /// </summary>
  628. /// <param name="path">The path.</param>
  629. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  630. /// <returns>
  631. /// File attributes
  632. /// </returns>
  633. internal SftpFileAttributes RequestStat(string path, bool nullOnError = false)
  634. {
  635. SftpFileAttributes attributes = null;
  636. using (var wait = new AutoResetEvent(false))
  637. {
  638. var request = new SftpStatRequest(this.NextRequestId, path,
  639. (response) =>
  640. {
  641. attributes = response.Attributes;
  642. wait.Set();
  643. },
  644. (response) =>
  645. {
  646. if (nullOnError)
  647. {
  648. wait.Set();
  649. }
  650. else
  651. {
  652. this.ThrowSftpException(response);
  653. }
  654. });
  655. this.SendRequest(request);
  656. this.WaitHandle(wait, this._operationTimeout);
  657. }
  658. return attributes;
  659. }
  660. /// <summary>
  661. /// Performs SSH_FXP_RENAME request.
  662. /// </summary>
  663. /// <param name="oldPath">The old path.</param>
  664. /// <param name="newPath">The new path.</param>
  665. internal void RequestRename(string oldPath, string newPath)
  666. {
  667. using (var wait = new AutoResetEvent(false))
  668. {
  669. var request = new SftpRenameRequest(this.NextRequestId, oldPath, newPath,
  670. (response) =>
  671. {
  672. if (response.StatusCode == StatusCodes.Ok)
  673. {
  674. wait.Set();
  675. }
  676. else
  677. {
  678. this.ThrowSftpException(response);
  679. }
  680. });
  681. this.SendRequest(request);
  682. this.WaitHandle(wait, this._operationTimeout);
  683. }
  684. }
  685. /// <summary>
  686. /// Performs SSH_FXP_READLINK request.
  687. /// </summary>
  688. /// <param name="path">The path.</param>
  689. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  690. /// <returns></returns>
  691. internal IDictionary<string, SftpFileAttributes> RequestReadLink(string path, bool nullOnError = false)
  692. {
  693. IDictionary<string, SftpFileAttributes> result = null;
  694. using (var wait = new AutoResetEvent(false))
  695. {
  696. var request = new SftpReadLinkRequest(this.NextRequestId, path,
  697. (response) =>
  698. {
  699. result = response.Files;
  700. wait.Set();
  701. },
  702. (response) =>
  703. {
  704. if (nullOnError)
  705. {
  706. wait.Set();
  707. }
  708. else
  709. {
  710. this.ThrowSftpException(response);
  711. }
  712. });
  713. this.SendRequest(request);
  714. this.WaitHandle(wait, this._operationTimeout);
  715. }
  716. return result;
  717. }
  718. /// <summary>
  719. /// Performs SSH_FXP_SYMLINK request.
  720. /// </summary>
  721. /// <param name="linkpath">The linkpath.</param>
  722. /// <param name="targetpath">The targetpath.</param>
  723. internal void RequestSymLink(string linkpath, string targetpath)
  724. {
  725. using (var wait = new AutoResetEvent(false))
  726. {
  727. var request = new SftpSymLinkRequest(this.NextRequestId, linkpath, targetpath,
  728. (response) =>
  729. {
  730. if (response.StatusCode == StatusCodes.Ok)
  731. {
  732. wait.Set();
  733. }
  734. else
  735. {
  736. this.ThrowSftpException(response);
  737. }
  738. });
  739. this.SendRequest(request);
  740. this.WaitHandle(wait, this._operationTimeout);
  741. }
  742. }
  743. #endregion
  744. private void ThrowSftpException(SftpStatusResponse response)
  745. {
  746. if (response.StatusCode == StatusCodes.PermissionDenied)
  747. {
  748. throw new SftpPermissionDeniedException(response.ErrorMessage);
  749. }
  750. else if (response.StatusCode == StatusCodes.NoSuchFile)
  751. {
  752. throw new SftpPathNotFoundException(response.ErrorMessage);
  753. }
  754. else
  755. {
  756. throw new SshException(response.ErrorMessage);
  757. }
  758. }
  759. private void HandleResponse(SftpResponse response)
  760. {
  761. SftpRequest request = null;
  762. lock (this._requests)
  763. {
  764. this._requests.TryGetValue(response.ResponseId, out request);
  765. if (request != null)
  766. {
  767. this._requests.Remove(response.ResponseId);
  768. }
  769. }
  770. if (request == null)
  771. throw new InvalidOperationException("Invalid response.");
  772. request.Complete(response);
  773. }
  774. private void Session_Disconnected(object sender, EventArgs e)
  775. {
  776. this.RaiseError(new SshException("Connection was lost"));
  777. }
  778. private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
  779. {
  780. this.RaiseError(e.Exception);
  781. }
  782. private void RaiseError(Exception error)
  783. {
  784. this._exception = error;
  785. this._errorOccuredWaitHandle.Set();
  786. if (this.ErrorOccured != null)
  787. {
  788. this.ErrorOccured(this, new ExceptionEventArgs(error));
  789. }
  790. }
  791. #region IDisposable Members
  792. private bool _isDisposed = false;
  793. /// <summary>
  794. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  795. /// </summary>
  796. public void Dispose()
  797. {
  798. Dispose(true);
  799. GC.SuppressFinalize(this);
  800. }
  801. /// <summary>
  802. /// Releases unmanaged and - optionally - managed resources
  803. /// </summary>
  804. /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  805. protected virtual void Dispose(bool disposing)
  806. {
  807. // Check to see if Dispose has already been called.
  808. if (!this._isDisposed)
  809. {
  810. if (this._channel != null)
  811. {
  812. this._channel.DataReceived -= Channel_DataReceived;
  813. this._channel.Dispose();
  814. this._channel = null;
  815. }
  816. this._session.ErrorOccured -= Session_ErrorOccured;
  817. this._session.Disconnected -= Session_Disconnected;
  818. // If disposing equals true, dispose all managed
  819. // and unmanaged resources.
  820. if (disposing)
  821. {
  822. // Dispose managed resources.
  823. if (this._errorOccuredWaitHandle != null)
  824. {
  825. this._errorOccuredWaitHandle.Dispose();
  826. this._errorOccuredWaitHandle = null;
  827. }
  828. if (this._sftpVersionConfirmed != null)
  829. {
  830. this._sftpVersionConfirmed.Dispose();
  831. this._sftpVersionConfirmed = null;
  832. }
  833. }
  834. // Note disposing has been done.
  835. _isDisposed = true;
  836. }
  837. }
  838. /// <summary>
  839. /// Releases unmanaged resources and performs other cleanup operations before the
  840. /// <see cref="SftpSession"/> is reclaimed by garbage collection.
  841. /// </summary>
  842. ~SftpSession()
  843. {
  844. // Do not re-create Dispose clean-up code here.
  845. // Calling Dispose(false) is optimal in terms of
  846. // readability and maintainability.
  847. Dispose(false);
  848. }
  849. #endregion
  850. }
  851. }