2
0

SftpSession.cs 31 KB

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